diff --git a/Assets/Tests/Editor/PausePointTests.cs b/Assets/Tests/Editor/PausePointTests.cs
index eb09a7f0f1..db2f5884b1 100644
--- a/Assets/Tests/Editor/PausePointTests.cs
+++ b/Assets/Tests/Editor/PausePointTests.cs
@@ -784,6 +784,7 @@ public async Task Enable_WhenFileAndLineResolveToRealMethod_PatchesAndCapturesVa
Assert.That(response.Success, Is.True);
Assert.That(response.Id, Is.EqualTo($"{FixtureFilePath}:{FixtureLine}"));
Assert.That(response.ResolvedLine, Is.EqualTo(FixtureLine));
+ Assert.That(response.ResolvedLineText, Is.EqualTo("return sum;"));
Assert.That(response.ResolvedMethod, Does.Contain("Add"));
EnableBySourceLocationFixture fixture = new();
diff --git a/Assets/Tests/Editor/SourcePausePointSourceLineReaderTests.cs b/Assets/Tests/Editor/SourcePausePointSourceLineReaderTests.cs
new file mode 100644
index 0000000000..a9e6b9085d
--- /dev/null
+++ b/Assets/Tests/Editor/SourcePausePointSourceLineReaderTests.cs
@@ -0,0 +1,66 @@
+using System.IO;
+
+using NUnit.Framework;
+
+using io.github.hatayama.UnityCliLoop.FirstPartyTools;
+
+namespace io.github.hatayama.UnityCliLoop.Tests.Editor
+{
+ ///
+ /// Verifies SourcePausePointSourceLineReader reads the trimmed text of a specific source
+ /// line from disk, and degrades to an empty string for missing files or out-of-range lines.
+ ///
+ [TestFixture]
+ public sealed class SourcePausePointSourceLineReaderTests
+ {
+ private string _tempFilePath;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _tempFilePath = Path.GetTempFileName();
+ File.WriteAllLines(_tempFilePath, new[] { "line one", " line two ", "line three" });
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ File.Delete(_tempFilePath);
+ }
+
+ [Test]
+ public void ReadLineText_WhenLineExists_ReturnsTrimmedText()
+ {
+ // Verifies the requested 1-based line is read and surrounding whitespace is trimmed.
+ string result = SourcePausePointSourceLineReader.ReadLineText(_tempFilePath, 2);
+
+ Assert.That(result, Is.EqualTo("line two"));
+ }
+
+ [Test]
+ public void ReadLineText_WhenLineNumberExceedsFileLength_ReturnsEmpty()
+ {
+ // Verifies a line number past the end of the file degrades to an empty string instead of throwing.
+ string result = SourcePausePointSourceLineReader.ReadLineText(_tempFilePath, 999);
+
+ Assert.That(result, Is.Empty);
+ }
+
+ [Test]
+ public void ReadLineText_WhenFileDoesNotExist_ReturnsEmpty()
+ {
+ // Verifies a missing file degrades to an empty string instead of throwing.
+ string result = SourcePausePointSourceLineReader.ReadLineText("/nonexistent/path/does-not-exist.cs", 1);
+
+ Assert.That(result, Is.Empty);
+ }
+
+ [Test]
+ public void ReadLineText_WhenLineNumberIsZeroOrNegative_ReturnsEmpty()
+ {
+ // Verifies non-positive line numbers (invalid 1-based input) degrade to an empty string.
+ Assert.That(SourcePausePointSourceLineReader.ReadLineText(_tempFilePath, 0), Is.Empty);
+ Assert.That(SourcePausePointSourceLineReader.ReadLineText(_tempFilePath, -1), Is.Empty);
+ }
+ }
+}
diff --git a/Assets/Tests/Editor/SourcePausePointSourceLineReaderTests.cs.meta b/Assets/Tests/Editor/SourcePausePointSourceLineReaderTests.cs.meta
new file mode 100644
index 0000000000..8ec0374010
--- /dev/null
+++ b/Assets/Tests/Editor/SourcePausePointSourceLineReaderTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6a186e19a9d9426baad3d8fc1bdd1439
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Packages/src/Editor/FirstPartyTools/PausePoint/PausePointTools.cs b/Packages/src/Editor/FirstPartyTools/PausePoint/PausePointTools.cs
index be944c4c3d..c700dc5e18 100644
--- a/Packages/src/Editor/FirstPartyTools/PausePoint/PausePointTools.cs
+++ b/Packages/src/Editor/FirstPartyTools/PausePoint/PausePointTools.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -48,6 +49,7 @@ public class PausePointResponse : UnityCliLoopToolResponse
{
public string Id { get; set; } = string.Empty;
public int ResolvedLine { get; set; }
+ public string ResolvedLineText { get; set; } = string.Empty;
public string ResolvedMethod { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public bool IsEnabled { get; set; }
@@ -354,11 +356,22 @@ private static PausePointResponse EnableBySourceLocation(EnablePausePointSchema
parameters.MaxHistory);
PausePointResponse response = PausePointResponse.FromSnapshot(snapshot);
response.ResolvedLine = resolveResult.Resolution.ResolvedLine;
+ response.ResolvedLineText = ReadResolvedLineText(parameters.File, resolveResult.Resolution.ResolvedLine);
response.ResolvedMethod = resolveResult.Resolution.MethodDisplayName;
response.Warning = MergeWarnings(CreateEnableWarning(), patchResult.Warning);
return response;
}
+ // The resolved line can be rounded forward from the requested line (the Resolver picks
+ // the closest sequence point on or after it), so returning the actual source text lets
+ // the caller notice a mismatch immediately instead of assuming the requested line hit.
+ private static string ReadResolvedLineText(string requestedFile, int resolvedLine)
+ {
+ string normalizedFile = SourcePausePointPathNormalizer.ToForwardSlashes(requestedFile);
+ string absoluteFilePath = Path.Combine(UnityCliLoopPathResolver.GetProjectRoot(), normalizedFile);
+ return SourcePausePointSourceLineReader.ReadLineText(absoluteFilePath, resolvedLine);
+ }
+
// The derived id must use the originally requested file/line (not the resolved/rounded
// line) so repeated calls at the same requested location stay idempotent.
private static string BuildSourcePausePointId(string file, int line)
diff --git a/Packages/src/Editor/FirstPartyTools/PausePoint/SourcePausePointSourceLineReader.cs b/Packages/src/Editor/FirstPartyTools/PausePoint/SourcePausePointSourceLineReader.cs
new file mode 100644
index 0000000000..a44515b803
--- /dev/null
+++ b/Packages/src/Editor/FirstPartyTools/PausePoint/SourcePausePointSourceLineReader.cs
@@ -0,0 +1,23 @@
+using System.IO;
+using System.Linq;
+
+namespace io.github.hatayama.UnityCliLoop.FirstPartyTools
+{
+ ///
+ /// Reads a single source line's trimmed text from disk, so a pause-point response can show
+ /// the AI agent exactly what code the resolved (possibly rounded-forward) line number maps to.
+ ///
+ internal static class SourcePausePointSourceLineReader
+ {
+ public static string ReadLineText(string absoluteFilePath, int lineNumber)
+ {
+ if (string.IsNullOrEmpty(absoluteFilePath) || lineNumber <= 0 || !File.Exists(absoluteFilePath))
+ {
+ return string.Empty;
+ }
+
+ string line = File.ReadLines(absoluteFilePath).Skip(lineNumber - 1).FirstOrDefault();
+ return line != null ? line.Trim() : string.Empty;
+ }
+ }
+}
diff --git a/Packages/src/Editor/FirstPartyTools/PausePoint/SourcePausePointSourceLineReader.cs.meta b/Packages/src/Editor/FirstPartyTools/PausePoint/SourcePausePointSourceLineReader.cs.meta
new file mode 100644
index 0000000000..a480b48fc5
--- /dev/null
+++ b/Packages/src/Editor/FirstPartyTools/PausePoint/SourcePausePointSourceLineReader.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 54ab67e3725046758bf545af7361d859
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: