@namzu/sdk 62.6.0 → 62.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/dist/agents/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +5 -0
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/agents/examples/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/examples/SupervisorAgent.js +1 -0
- package/dist/agents/examples/SupervisorAgent.js.map +1 -1
- package/dist/agents/explore.d.ts.map +1 -1
- package/dist/agents/explore.js +2 -1
- package/dist/agents/explore.js.map +1 -1
- package/dist/compaction/index.d.ts +1 -1
- package/dist/compaction/index.d.ts.map +1 -1
- package/dist/compaction/index.js +1 -1
- package/dist/compaction/index.js.map +1 -1
- package/dist/compaction/model-summary.js +2 -2
- package/dist/compaction/model-summary.js.map +1 -1
- package/dist/compaction/summary.d.ts +16 -1
- package/dist/compaction/summary.d.ts.map +1 -1
- package/dist/compaction/summary.js +25 -2
- package/dist/compaction/summary.js.map +1 -1
- package/dist/config/runtime.d.ts +8 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +15 -0
- package/dist/config/runtime.js.map +1 -1
- package/dist/execution/workflow/runtime.d.ts.map +1 -1
- package/dist/execution/workflow/runtime.js +3 -0
- package/dist/execution/workflow/runtime.js.map +1 -1
- package/dist/execution/workflow/types.d.ts +8 -0
- package/dist/execution/workflow/types.d.ts.map +1 -1
- package/dist/manager/agent/child-session.d.ts +12 -0
- package/dist/manager/agent/child-session.d.ts.map +1 -1
- package/dist/manager/agent/child-session.js +29 -0
- package/dist/manager/agent/child-session.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +8 -0
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/session/turn-recorder.d.ts +13 -2
- package/dist/manager/session/turn-recorder.d.ts.map +1 -1
- package/dist/manager/session/turn-recorder.js +57 -3
- package/dist/manager/session/turn-recorder.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +3 -3
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +5 -3
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +5 -1
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/retry.d.ts +25 -0
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +28 -0
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +3 -3
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +3 -3
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +4 -2
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +5 -2
- package/dist/public-tools.js.map +1 -1
- package/dist/runtime/query/auto-review/classifier.d.ts +7 -1
- package/dist/runtime/query/auto-review/classifier.d.ts.map +1 -1
- package/dist/runtime/query/auto-review/classifier.js +8 -4
- package/dist/runtime/query/auto-review/classifier.js.map +1 -1
- package/dist/runtime/query/auto-review/prompt.d.ts.map +1 -1
- package/dist/runtime/query/auto-review/prompt.js +2 -0
- package/dist/runtime/query/auto-review/prompt.js.map +1 -1
- package/dist/runtime/query/auto-review/script-evidence.d.ts +93 -0
- package/dist/runtime/query/auto-review/script-evidence.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/script-evidence.js +566 -0
- package/dist/runtime/query/auto-review/script-evidence.js.map +1 -0
- package/dist/runtime/query/checkpoint.d.ts +13 -0
- package/dist/runtime/query/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/checkpoint.js +37 -0
- package/dist/runtime/query/checkpoint.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +38 -1
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +130 -11
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/finalize-turn.d.ts +7 -1
- package/dist/runtime/query/finalize-turn.d.ts.map +1 -1
- package/dist/runtime/query/finalize-turn.js +2 -0
- package/dist/runtime/query/finalize-turn.js.map +1 -1
- package/dist/runtime/query/index.d.ts +8 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +7 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +32 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +158 -10
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts +12 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +48 -1
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js +5 -0
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +3 -3
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/prelude-lease.d.ts.map +1 -1
- package/dist/runtime/query/prelude-lease.js +22 -0
- package/dist/runtime/query/prelude-lease.js.map +1 -1
- package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
- package/dist/runtime/query/prepare-turn.js +13 -4
- package/dist/runtime/query/prepare-turn.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +29 -0
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/runtime/query/tooling.d.ts +2 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +1 -0
- package/dist/runtime/query/tooling.js.map +1 -1
- package/dist/runtime/query/turn/completion-audit.d.ts +27 -1
- package/dist/runtime/query/turn/completion-audit.d.ts.map +1 -1
- package/dist/runtime/query/turn/completion-audit.js +71 -6
- package/dist/runtime/query/turn/completion-audit.js.map +1 -1
- package/dist/runtime/query/turn/outcome-ledger.d.ts +113 -0
- package/dist/runtime/query/turn/outcome-ledger.d.ts.map +1 -0
- package/dist/runtime/query/turn/outcome-ledger.js +578 -0
- package/dist/runtime/query/turn/outcome-ledger.js.map +1 -0
- package/dist/sandbox/file-walk.d.ts.map +1 -1
- package/dist/sandbox/file-walk.js +34 -17
- package/dist/sandbox/file-walk.js.map +1 -1
- package/dist/sandbox/glob-segment.d.ts +35 -0
- package/dist/sandbox/glob-segment.d.ts.map +1 -0
- package/dist/sandbox/glob-segment.js +300 -0
- package/dist/sandbox/glob-segment.js.map +1 -0
- package/dist/store/session-log/index.d.ts +1 -0
- package/dist/store/session-log/index.d.ts.map +1 -1
- package/dist/store/session-log/index.js +1 -0
- package/dist/store/session-log/index.js.map +1 -1
- package/dist/store/session-log/turn-outcome.d.ts +37 -0
- package/dist/store/session-log/turn-outcome.d.ts.map +1 -0
- package/dist/store/session-log/turn-outcome.js +77 -0
- package/dist/store/session-log/turn-outcome.js.map +1 -0
- package/dist/tools/builtins/bash.js +1 -1
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/edit-apply.d.ts.map +1 -1
- package/dist/tools/builtins/edit-apply.js +24 -3
- package/dist/tools/builtins/edit-apply.js.map +1 -1
- package/dist/tools/builtins/edit.d.ts +31 -17
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +92 -27
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/glob.js +1 -1
- package/dist/tools/builtins/glob.js.map +1 -1
- package/dist/tools/builtins/grep.js +1 -1
- package/dist/tools/builtins/grep.js.map +1 -1
- package/dist/tools/builtins/notebook-guard.d.ts +19 -0
- package/dist/tools/builtins/notebook-guard.d.ts.map +1 -0
- package/dist/tools/builtins/notebook-guard.js +75 -0
- package/dist/tools/builtins/notebook-guard.js.map +1 -0
- package/dist/tools/builtins/read-render.d.ts.map +1 -1
- package/dist/tools/builtins/read-render.js +8 -2
- package/dist/tools/builtins/read-render.js.map +1 -1
- package/dist/tools/builtins/write-file.d.ts.map +1 -1
- package/dist/tools/builtins/write-file.js +14 -1
- package/dist/tools/builtins/write-file.js.map +1 -1
- package/dist/tools/coordinator/agent.d.ts.map +1 -1
- package/dist/tools/coordinator/agent.js +25 -6
- package/dist/tools/coordinator/agent.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +6 -1
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/coordinator/outcome-record.d.ts +22 -0
- package/dist/tools/coordinator/outcome-record.d.ts.map +1 -0
- package/dist/tools/coordinator/outcome-record.js +91 -0
- package/dist/tools/coordinator/outcome-record.js.map +1 -0
- package/dist/tools/defineTool.d.ts +2 -0
- package/dist/tools/defineTool.d.ts.map +1 -1
- package/dist/tools/defineTool.js +1 -0
- package/dist/tools/defineTool.js.map +1 -1
- package/dist/tools/file-read-tracker.d.ts +23 -2
- package/dist/tools/file-read-tracker.d.ts.map +1 -1
- package/dist/tools/file-read-tracker.js +37 -2
- package/dist/tools/file-read-tracker.js.map +1 -1
- package/dist/tools/untrusted-envelope.d.ts +6 -0
- package/dist/tools/untrusted-envelope.d.ts.map +1 -1
- package/dist/tools/untrusted-envelope.js +22 -1
- package/dist/tools/untrusted-envelope.js.map +1 -1
- package/dist/toolsets/manager.d.ts.map +1 -1
- package/dist/toolsets/manager.js +3 -4
- package/dist/toolsets/manager.js.map +1 -1
- package/dist/toolsets/validation-issues.d.ts +27 -0
- package/dist/toolsets/validation-issues.d.ts.map +1 -0
- package/dist/toolsets/validation-issues.js +83 -0
- package/dist/toolsets/validation-issues.js.map +1 -0
- package/dist/turn/memory-recall.d.ts +13 -0
- package/dist/turn/memory-recall.d.ts.map +1 -1
- package/dist/turn/memory-recall.js +18 -0
- package/dist/turn/memory-recall.js.map +1 -1
- package/dist/types/agent/base.d.ts +19 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/agent/query.d.ts +2 -0
- package/dist/types/agent/query.d.ts.map +1 -1
- package/dist/types/common/index.d.ts +17 -0
- package/dist/types/common/index.d.ts.map +1 -1
- package/dist/types/common/index.js +8 -1
- package/dist/types/common/index.js.map +1 -1
- package/dist/types/hitl/index.d.ts +22 -0
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/session/config.d.ts +20 -0
- package/dist/types/session/config.d.ts.map +1 -1
- package/dist/types/session/events.d.ts +3 -3
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/index.d.ts +1 -0
- package/dist/types/session/index.d.ts.map +1 -1
- package/dist/types/session/index.js +1 -0
- package/dist/types/session/index.js.map +1 -1
- package/dist/types/session/outcome.d.ts +95 -0
- package/dist/types/session/outcome.d.ts.map +1 -0
- package/dist/types/session/outcome.js +45 -0
- package/dist/types/session/outcome.js.map +1 -0
- package/dist/types/session/prepare-step.d.ts +2 -0
- package/dist/types/session/prepare-step.d.ts.map +1 -1
- package/dist/types/session/records.d.ts +167 -2
- package/dist/types/session/records.d.ts.map +1 -1
- package/dist/types/session/records.js +42 -0
- package/dist/types/session/records.js.map +1 -1
- package/dist/types/session/turn.d.ts +18 -0
- package/dist/types/session/turn.d.ts.map +1 -1
- package/dist/types/session/turn.js.map +1 -1
- package/dist/types/tool/index.d.ts +30 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/package.json +2 -2
- package/src/agents/QueryAgent.ts +5 -0
- package/src/agents/examples/SupervisorAgent.ts +1 -0
- package/src/agents/explore.ts +2 -1
- package/src/compaction/index.ts +1 -0
- package/src/compaction/model-summary.ts +2 -2
- package/src/compaction/summary.ts +30 -2
- package/src/config/runtime.ts +15 -0
- package/src/execution/workflow/runtime.ts +4 -0
- package/src/execution/workflow/types.ts +9 -0
- package/src/manager/agent/child-session.ts +36 -0
- package/src/manager/agent/lifecycle.ts +9 -0
- package/src/manager/session/turn-recorder.ts +62 -3
- package/src/pricing/rates.source.json +1 -1
- package/src/prompt/coding-agent-doctrine.ts +5 -3
- package/src/provider/fallback.ts +6 -1
- package/src/provider/retry.ts +51 -0
- package/src/public-runtime.ts +10 -1
- package/src/public-tools.ts +6 -1
- package/src/runtime/query/auto-review/classifier.ts +21 -5
- package/src/runtime/query/auto-review/prompt.ts +2 -0
- package/src/runtime/query/auto-review/script-evidence.ts +618 -0
- package/src/runtime/query/checkpoint.ts +53 -1
- package/src/runtime/query/executor.ts +159 -19
- package/src/runtime/query/finalize-turn.ts +8 -1
- package/src/runtime/query/index.ts +19 -0
- package/src/runtime/query/iteration/index.ts +167 -14
- package/src/runtime/query/iteration/phases/compaction.ts +53 -3
- package/src/runtime/query/iteration/phases/context.ts +6 -0
- package/src/runtime/query/iteration/stream-turn.ts +3 -3
- package/src/runtime/query/prelude-lease.ts +19 -0
- package/src/runtime/query/prepare-turn.ts +13 -4
- package/src/runtime/query/review-policy.ts +33 -0
- package/src/runtime/query/tooling.ts +3 -0
- package/src/runtime/query/turn/completion-audit.ts +76 -5
- package/src/runtime/query/turn/outcome-ledger.ts +633 -0
- package/src/sandbox/file-walk.ts +38 -17
- package/src/sandbox/glob-segment.ts +309 -0
- package/src/store/session-log/index.ts +7 -0
- package/src/store/session-log/turn-outcome.ts +84 -0
- package/src/tools/builtins/bash.ts +1 -1
- package/src/tools/builtins/edit-apply.ts +26 -4
- package/src/tools/builtins/edit.ts +106 -41
- package/src/tools/builtins/glob.ts +1 -1
- package/src/tools/builtins/grep.ts +1 -1
- package/src/tools/builtins/notebook-guard.ts +76 -0
- package/src/tools/builtins/read-render.ts +12 -2
- package/src/tools/builtins/write-file.ts +12 -1
- package/src/tools/coordinator/agent.ts +31 -7
- package/src/tools/coordinator/index.ts +6 -1
- package/src/tools/coordinator/outcome-record.ts +93 -0
- package/src/tools/defineTool.ts +3 -0
- package/src/tools/file-read-tracker.ts +51 -2
- package/src/tools/untrusted-envelope.ts +23 -1
- package/src/toolsets/manager.ts +6 -10
- package/src/toolsets/validation-issues.ts +97 -0
- package/src/turn/memory-recall.ts +28 -0
- package/src/types/agent/base.ts +20 -0
- package/src/types/agent/query.ts +2 -0
- package/src/types/common/index.ts +25 -1
- package/src/types/hitl/index.ts +27 -0
- package/src/types/session/config.ts +20 -0
- package/src/types/session/events.ts +3 -3
- package/src/types/session/index.ts +1 -0
- package/src/types/session/outcome.ts +139 -0
- package/src/types/session/prepare-step.ts +2 -0
- package/src/types/session/records.ts +67 -1
- package/src/types/session/turn.ts +19 -0
- package/src/types/tool/index.ts +30 -0
package/src/sandbox/file-walk.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { isAbsolute, join, posix } from 'node:path'
|
|
2
2
|
import braceExpansion from 'brace-expansion'
|
|
3
|
-
import { GLOBSTAR, Minimatch } from 'minimatch'
|
|
4
3
|
import type {
|
|
5
4
|
SandboxExecOptions,
|
|
6
5
|
SandboxExecResult,
|
|
@@ -10,11 +9,15 @@ import type {
|
|
|
10
9
|
} from '../types/sandbox/index.js'
|
|
11
10
|
import { subscribeToAbort } from '../utils/abort.js'
|
|
12
11
|
import { FILE_WALK_PROGRAM } from './file-walk-program.js'
|
|
12
|
+
import { compileSegment } from './glob-segment.js'
|
|
13
13
|
import { VCS_NAMES, createIgnoreStack } from './ignore-matcher.js'
|
|
14
14
|
|
|
15
15
|
/** Entries a walk examines before it stops with what it has: a safety valve, not a budget. */
|
|
16
16
|
export const DEFAULT_MAX_VISITED_ENTRIES = 500_000
|
|
17
17
|
|
|
18
|
+
const GLOBSTAR = Symbol('globstar')
|
|
19
|
+
type Segment = string | { source: string; flags: string } | typeof GLOBSTAR
|
|
20
|
+
|
|
18
21
|
interface WalkPlan {
|
|
19
22
|
root: string
|
|
20
23
|
prefix: string[]
|
|
@@ -62,22 +65,26 @@ function planWalk(root: string, options: SandboxWalkFilesOptions): WalkPlan {
|
|
|
62
65
|
// that cap can silently truncate before our extra branch proves incompleteness.
|
|
63
66
|
const branches = expandBraces(pattern, { max: 257, maxLength: Number.POSITIVE_INFINITY })
|
|
64
67
|
if (branches.length > 256) throw new RangeError('File walk pattern exceeds 256 brace expansions')
|
|
65
|
-
const
|
|
68
|
+
const dot = options.includeHidden ?? false
|
|
69
|
+
const sets = branches.map((branch) => {
|
|
66
70
|
if (branch.startsWith('/') || /^[a-z]:/i.test(branch) || branch.split('/').includes('..')) {
|
|
67
71
|
throw new Error('Expanded file walk pattern escapes its root')
|
|
68
72
|
}
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
return branch
|
|
74
|
+
.split('/')
|
|
75
|
+
.filter(
|
|
76
|
+
(segment, index, all) => segment !== '.' && (segment !== '' || index === all.length - 1),
|
|
77
|
+
)
|
|
78
|
+
.reduce<Segment[]>((set, segment) => {
|
|
79
|
+
if (segment === '**') {
|
|
80
|
+
if (set[set.length - 1] !== GLOBSTAR) set.push(GLOBSTAR)
|
|
81
|
+
return set
|
|
82
|
+
}
|
|
83
|
+
const { literal, source } = compileSegment(segment, dot)
|
|
84
|
+
set.push(literal !== null ? literal : { source, flags: 'u' })
|
|
85
|
+
return set
|
|
86
|
+
}, [])
|
|
76
87
|
})
|
|
77
|
-
const sets = matchers.flatMap((matcher) => matcher.set)
|
|
78
|
-
if (sets.some((set) => set.some((segment) => segment === '..'))) {
|
|
79
|
-
throw new Error('Expanded file walk pattern escapes its root')
|
|
80
|
-
}
|
|
81
88
|
const patternDepth = Math.max(
|
|
82
89
|
1,
|
|
83
90
|
...sets.map((set) => (set.includes(GLOBSTAR) ? Number.POSITIVE_INFINITY : set.length)),
|
|
@@ -100,10 +107,24 @@ function planWalk(root: string, options: SandboxWalkFilesOptions): WalkPlan {
|
|
|
100
107
|
break
|
|
101
108
|
prefix.push(segment)
|
|
102
109
|
}
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
const hiddenGuard = dot ? '' : '(?!\\.)'
|
|
111
|
+
const expressions = sets.map((set) => ({
|
|
112
|
+
source: `^${set
|
|
113
|
+
.map((segment, index) => {
|
|
114
|
+
const last = index === set.length - 1
|
|
115
|
+
if (segment === GLOBSTAR) {
|
|
116
|
+
const run = `(?:${hiddenGuard}[^/]+/)*`
|
|
117
|
+
return last ? `${run}${hiddenGuard}[^/]+` : run
|
|
118
|
+
}
|
|
119
|
+
const body =
|
|
120
|
+
typeof segment === 'string'
|
|
121
|
+
? segment.replace(/[\\^$.*+?()[\]{}|/]/g, '\\$&')
|
|
122
|
+
: segment.source.slice(1, -1)
|
|
123
|
+
return last ? `(?:${body})` : `(?:${body})/`
|
|
124
|
+
})
|
|
125
|
+
.join('')}$`,
|
|
126
|
+
flags: 'u',
|
|
127
|
+
}))
|
|
107
128
|
return {
|
|
108
129
|
root,
|
|
109
130
|
prefix,
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiles one path segment of a glob (no `/` in it) to the regular expression
|
|
3
|
+
* the file walk tests names with, in the same dialect the sandbox guest runs.
|
|
4
|
+
* It covers `*`, `?`, `[...]` classes (with `!`/`^` negation, ranges and POSIX
|
|
5
|
+
* classes), the five extglob forms and `\` escapes. A segment with no magic in
|
|
6
|
+
* it comes back as the plain name to compare against.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Written for the `u` flag, which every compiled expression carries.
|
|
10
|
+
const POSIX_CLASSES: ReadonlyMap<string, string> = new Map(
|
|
11
|
+
Object.entries({
|
|
12
|
+
alnum: '\\p{L}\\p{Nl}\\p{Nd}',
|
|
13
|
+
alpha: '\\p{L}\\p{Nl}',
|
|
14
|
+
ascii: '\\x00-\\x7f',
|
|
15
|
+
blank: '\\p{Zs}\\t',
|
|
16
|
+
cntrl: '\\p{Cc}',
|
|
17
|
+
digit: '\\p{Nd}',
|
|
18
|
+
graph: '\\x21-\\x7e',
|
|
19
|
+
lower: '\\p{Ll}',
|
|
20
|
+
print: '\\x20-\\x7e',
|
|
21
|
+
punct: '\\p{P}\\p{S}',
|
|
22
|
+
space: '\\s',
|
|
23
|
+
upper: '\\p{Lu}',
|
|
24
|
+
word: '\\p{L}\\p{Nl}\\p{Nd}_',
|
|
25
|
+
xdigit: '0-9a-fA-F',
|
|
26
|
+
}),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
export interface CompiledSegment {
|
|
30
|
+
/** Set when the segment has no magic: the exact name it matches. */
|
|
31
|
+
literal: string | null
|
|
32
|
+
/** Source of an expression anchored with `^` and `$`, for a segment with magic. */
|
|
33
|
+
source: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The longest segment, in UTF-16 code units, that `compileSegment` accepts. A
|
|
38
|
+
* file name is at most 255 bytes on the file systems Namzu runs on, so a
|
|
39
|
+
* pattern for one name beyond four times that is never legitimate, while
|
|
40
|
+
* compiling a segment costs time in proportion to the square of its length.
|
|
41
|
+
*/
|
|
42
|
+
export const MAX_GLOB_SEGMENT_LENGTH = 1024
|
|
43
|
+
|
|
44
|
+
/** How much of an over-long pattern an error message shows, in code points. */
|
|
45
|
+
const PREVIEW_LENGTH = 60
|
|
46
|
+
|
|
47
|
+
/** The first `PREVIEW_LENGTH` code points of `text`, never cutting a surrogate pair. */
|
|
48
|
+
const preview = (text: string) =>
|
|
49
|
+
Array.from(text.slice(0, PREVIEW_LENGTH * 2))
|
|
50
|
+
.slice(0, PREVIEW_LENGTH)
|
|
51
|
+
.join('')
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Raised for a segment that repeats a group (`*( )` or `+( )`) which could match
|
|
55
|
+
* the same text more than one way. Such a group backtracks exponentially on a
|
|
56
|
+
* name that nearly matches, and glob patterns come from untrusted input.
|
|
57
|
+
*/
|
|
58
|
+
export class GlobTooComplexError extends Error {
|
|
59
|
+
readonly segment: string
|
|
60
|
+
constructor(segment: string, reason: 'ambiguous' | 'stretch' | 'length' = 'ambiguous') {
|
|
61
|
+
super(
|
|
62
|
+
reason === 'length'
|
|
63
|
+
? `The pattern '${preview(segment)}...' is too long (${segment.length} characters; at most ${MAX_GLOB_SEGMENT_LENGTH} between slashes); shorten it or split it across folders.`
|
|
64
|
+
: reason === 'stretch'
|
|
65
|
+
? `The pattern '${segment}' has more than three wildcards that can stretch; split it across folders or use fewer *.`
|
|
66
|
+
: `The pattern '${segment}' repeats a group that can match the same text more than one way; use ** or {a,b} instead.`,
|
|
67
|
+
)
|
|
68
|
+
this.name = 'GlobTooComplexError'
|
|
69
|
+
// An over-long pattern is not kept whole: the error may be logged.
|
|
70
|
+
this.segment = reason === 'length' ? preview(segment) : segment
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const escapeRegex = (text: string) => text.replace(/[\\^$.*+?()[\]{}|/]/g, '\\$&')
|
|
75
|
+
const escapeInClass = (text: string) => text.replace(/[\\^\]\[/-]/g, '\\$&')
|
|
76
|
+
|
|
77
|
+
interface Parsed {
|
|
78
|
+
source: string
|
|
79
|
+
literal: string
|
|
80
|
+
magic: boolean
|
|
81
|
+
/** Unbounded runs: each `*` (consecutive ones collapsed), `*( )` and `+( )`. */
|
|
82
|
+
runs: number
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Index of the `)` closing the group opened just before `from`, or -1. */
|
|
86
|
+
function closeGroup(text: string, from: number): number {
|
|
87
|
+
let depth = 1
|
|
88
|
+
for (let index = from; index < text.length; index += 1) {
|
|
89
|
+
const ch = text[index]
|
|
90
|
+
if (ch === '\\') index += 1
|
|
91
|
+
else if (ch === '[') {
|
|
92
|
+
const end = classEnd(text, index)
|
|
93
|
+
if (end > 0) index = end
|
|
94
|
+
} else if (ch === '(') depth += 1
|
|
95
|
+
else if (ch === ')' && --depth === 0) return index
|
|
96
|
+
}
|
|
97
|
+
return -1
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Index of the `]` closing the class opened at `start`, or -1 when it never closes. */
|
|
101
|
+
function classEnd(text: string, start: number): number {
|
|
102
|
+
let index = start + 1
|
|
103
|
+
if (text[index] === '!' || text[index] === '^') index += 1
|
|
104
|
+
if (text[index] === ']') index += 1
|
|
105
|
+
for (; index < text.length; index += 1) {
|
|
106
|
+
const ch = text[index]
|
|
107
|
+
if (ch === '\\') index += 1
|
|
108
|
+
else if (ch === '[' && text[index + 1] === ':') {
|
|
109
|
+
const end = text.indexOf(':]', index + 2)
|
|
110
|
+
if (end > 0 && POSIX_CLASSES.has(text.slice(index + 2, end))) index = end + 1
|
|
111
|
+
} else if (ch === ']') return index
|
|
112
|
+
}
|
|
113
|
+
return -1
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function splitAlternatives(body: string): string[] {
|
|
117
|
+
const parts: string[] = []
|
|
118
|
+
let depth = 0
|
|
119
|
+
let current = ''
|
|
120
|
+
for (let index = 0; index < body.length; index += 1) {
|
|
121
|
+
const ch = body[index] as string
|
|
122
|
+
if (ch === '\\') {
|
|
123
|
+
current += ch + (body[index + 1] ?? '')
|
|
124
|
+
index += 1
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
if (ch === '[') {
|
|
128
|
+
const end = classEnd(body, index)
|
|
129
|
+
if (end > 0) {
|
|
130
|
+
current += body.slice(index, end + 1)
|
|
131
|
+
index = end
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (ch === '(') depth += 1
|
|
136
|
+
if (ch === ')') depth -= 1
|
|
137
|
+
if (ch === '|' && depth === 0) {
|
|
138
|
+
parts.push(current)
|
|
139
|
+
current = ''
|
|
140
|
+
} else current += ch
|
|
141
|
+
}
|
|
142
|
+
parts.push(current)
|
|
143
|
+
return parts
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function classSource(body: string): string {
|
|
147
|
+
let index = 0
|
|
148
|
+
let negate = false
|
|
149
|
+
if (body[0] === '!' || body[0] === '^') {
|
|
150
|
+
negate = true
|
|
151
|
+
index = 1
|
|
152
|
+
}
|
|
153
|
+
let out = ''
|
|
154
|
+
for (; index < body.length; index += 1) {
|
|
155
|
+
const ch = body[index] as string
|
|
156
|
+
if (ch === '[' && body[index + 1] === ':') {
|
|
157
|
+
const end = body.indexOf(':]', index + 2)
|
|
158
|
+
const name = end > 0 ? body.slice(index + 2, end) : ''
|
|
159
|
+
const members = POSIX_CLASSES.get(name)
|
|
160
|
+
if (members !== undefined) {
|
|
161
|
+
out += members
|
|
162
|
+
index = end + 1
|
|
163
|
+
continue
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (ch === '\\' && index + 1 < body.length) {
|
|
167
|
+
index += 1
|
|
168
|
+
out += escapeInClass(body[index] as string)
|
|
169
|
+
} else if (ch === '-' && out !== '' && index + 1 < body.length) out += '-'
|
|
170
|
+
else out += escapeInClass(ch)
|
|
171
|
+
}
|
|
172
|
+
const source = `[${negate ? '^' : ''}${out}]`
|
|
173
|
+
try {
|
|
174
|
+
new RegExp(source, 'u')
|
|
175
|
+
} catch {
|
|
176
|
+
// An out-of-order range such as `[z-a]` matches no name rather than failing the walk.
|
|
177
|
+
return '(?!)'
|
|
178
|
+
}
|
|
179
|
+
return source
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** `guard`, when set, is put before each alternative of a group at the very start that does not itself spell out a leading `.`. */
|
|
183
|
+
function parse(text: string, guard = ''): Parsed {
|
|
184
|
+
let source = ''
|
|
185
|
+
let literal = ''
|
|
186
|
+
let magic = false
|
|
187
|
+
let runs = 0
|
|
188
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
189
|
+
const ch = text[index] as string
|
|
190
|
+
const next = text[index + 1]
|
|
191
|
+
if (ch === '\\') {
|
|
192
|
+
if (next === undefined) {
|
|
193
|
+
source += '\\\\'
|
|
194
|
+
literal += '\\'
|
|
195
|
+
} else {
|
|
196
|
+
source += escapeRegex(next)
|
|
197
|
+
literal += next
|
|
198
|
+
index += 1
|
|
199
|
+
}
|
|
200
|
+
} else if (ch === '*' && next !== '(') {
|
|
201
|
+
while (text[index + 1] === '*') index += 1
|
|
202
|
+
source += '[^/]*?'
|
|
203
|
+
runs += 1
|
|
204
|
+
magic = true
|
|
205
|
+
} else if (ch === '?' && next !== '(') {
|
|
206
|
+
source += '[^/]'
|
|
207
|
+
magic = true
|
|
208
|
+
} else if (ch === '[') {
|
|
209
|
+
const end = classEnd(text, index)
|
|
210
|
+
if (end < 0) {
|
|
211
|
+
source += '\\['
|
|
212
|
+
literal += '['
|
|
213
|
+
} else {
|
|
214
|
+
source += classSource(text.slice(index + 1, end))
|
|
215
|
+
magic = true
|
|
216
|
+
index = end
|
|
217
|
+
}
|
|
218
|
+
} else if ('?*+@!'.includes(ch) && next === '(') {
|
|
219
|
+
const end = closeGroup(text, index + 2)
|
|
220
|
+
if (end < 0) {
|
|
221
|
+
source += escapeRegex(ch)
|
|
222
|
+
literal += ch
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
225
|
+
const parts = splitAlternatives(text.slice(index + 2, end)).map((a) => ({
|
|
226
|
+
a,
|
|
227
|
+
parsed: parse(a),
|
|
228
|
+
}))
|
|
229
|
+
// A repeated group is linear only when each repetition can be read one way:
|
|
230
|
+
// plain literal alternatives, none a prefix of another (and none empty).
|
|
231
|
+
if (ch === '*' || ch === '+') {
|
|
232
|
+
const literals = parts.map((part) => part.parsed.literal)
|
|
233
|
+
// A lone class is one character wide, so each repetition has one reading.
|
|
234
|
+
const lone = parts.length === 1 && isOneClass(parts[0]?.a ?? '')
|
|
235
|
+
const plain = lone || parts.every((part) => !part.parsed.magic)
|
|
236
|
+
const prefixFree =
|
|
237
|
+
lone ||
|
|
238
|
+
literals.every(
|
|
239
|
+
(one, i) =>
|
|
240
|
+
one !== '' && literals.every((other, j) => i === j || !other.startsWith(one)),
|
|
241
|
+
)
|
|
242
|
+
if (!plain || !prefixFree) throw new GlobTooComplexError(text)
|
|
243
|
+
}
|
|
244
|
+
const alternatives = parts.map(
|
|
245
|
+
({ a, parsed }) => (index === 0 && !startsWithDot(a) ? guard : '') + parsed.source,
|
|
246
|
+
)
|
|
247
|
+
const group = `(?:${alternatives.join('|')})`
|
|
248
|
+
runs +=
|
|
249
|
+
parts.reduce((sum, part) => sum + part.parsed.runs, 0) + (ch === '*' || ch === '+' ? 1 : 0)
|
|
250
|
+
magic = true
|
|
251
|
+
if (ch === '!') {
|
|
252
|
+
const tail = parse(text.slice(end + 1))
|
|
253
|
+
const rest = tail.source
|
|
254
|
+
source += `(?:(?!${group}${rest}$)[^/]*?)${rest}`
|
|
255
|
+
return { source, literal, magic, runs: runs + 1 + tail.runs }
|
|
256
|
+
}
|
|
257
|
+
source += ch === '@' ? group : `${group}${ch === '?' ? '?' : ch === '*' ? '*' : '+'}`
|
|
258
|
+
index = end
|
|
259
|
+
} else {
|
|
260
|
+
source += escapeRegex(ch)
|
|
261
|
+
literal += ch
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return { source, literal, magic, runs }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Whether the text is exactly one closed `[...]` class. */
|
|
268
|
+
function isOneClass(text: string): boolean {
|
|
269
|
+
return text.startsWith('[') && classEnd(text, 0) === text.length - 1
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Whether every name this segment can match begins with a literal `.` the pattern spells out. */
|
|
273
|
+
function startsWithDot(text: string): boolean {
|
|
274
|
+
if (text.startsWith('.') || text.startsWith('\\.')) return true
|
|
275
|
+
if (text.startsWith('[')) {
|
|
276
|
+
const end = classEnd(text, 0)
|
|
277
|
+
return end > 0 && text.slice(1, end) === '.'
|
|
278
|
+
}
|
|
279
|
+
return false
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Whether the segment is a single `?(`, `*(`, `@(` or `+(` group, whose alternatives carry the leading-dot guard themselves. */
|
|
283
|
+
function isOneGroup(text: string): boolean {
|
|
284
|
+
return (
|
|
285
|
+
'?*@+'.includes(text[0] ?? '') && text[1] === '(' && closeGroup(text, 2) === text.length - 1
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* `dot` lets a wildcard match a name that starts with `.`; without it only a
|
|
291
|
+
* segment that itself begins with a literal `.` can.
|
|
292
|
+
*/
|
|
293
|
+
export function compileSegment(segment: string, dot: boolean): CompiledSegment {
|
|
294
|
+
if (segment.length > MAX_GLOB_SEGMENT_LENGTH) throw new GlobTooComplexError(segment, 'length')
|
|
295
|
+
const guard = dot ? '' : '(?!\\.)'
|
|
296
|
+
let parsed: Parsed
|
|
297
|
+
try {
|
|
298
|
+
parsed = parse(segment, guard)
|
|
299
|
+
} catch (error) {
|
|
300
|
+
throw error instanceof GlobTooComplexError
|
|
301
|
+
? new GlobTooComplexError(segment, 'ambiguous')
|
|
302
|
+
: error
|
|
303
|
+
}
|
|
304
|
+
if (parsed.runs > 3) throw new GlobTooComplexError(segment, 'stretch')
|
|
305
|
+
if (!parsed.magic) return { literal: parsed.literal, source: `^${parsed.source}$` }
|
|
306
|
+
const grouped = (segment.startsWith('@(') || segment.startsWith('+(')) && !dot
|
|
307
|
+
const lead = grouped || dot || startsWithDot(segment) || isOneGroup(segment) ? '' : guard
|
|
308
|
+
return { literal: null, source: `^${lead}(?:${parsed.source})$` }
|
|
309
|
+
}
|
|
@@ -61,6 +61,13 @@ export {
|
|
|
61
61
|
withMessageId,
|
|
62
62
|
} from './fold.js'
|
|
63
63
|
export { type TornTailRepair, repairRecordDraft } from './heal.js'
|
|
64
|
+
export {
|
|
65
|
+
foldChildOutcomes,
|
|
66
|
+
foldTurnOutcomes,
|
|
67
|
+
parseTurnOutcome,
|
|
68
|
+
readOutcomeProgress,
|
|
69
|
+
readTurnOutcome,
|
|
70
|
+
} from './turn-outcome.js'
|
|
64
71
|
export {
|
|
65
72
|
type ReleaseHeldSessionLeasesOptions,
|
|
66
73
|
type ReleaseHeldSessionLeasesResult,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { CheckpointId, SessionId, TurnId } from '../../types/ids/index.js'
|
|
2
|
+
import { type TurnOutcome, TurnOutcomeSchema } from '../../types/session/outcome.js'
|
|
3
|
+
import type { SessionRecord } from '../../types/session/records.js'
|
|
4
|
+
import type { SessionLog } from './core.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Reading the turn's outcome record back out of the session log.
|
|
8
|
+
*
|
|
9
|
+
* It lives in three records of its own (`turn_outcome_recorded`,
|
|
10
|
+
* `child_outcome_recorded`, `outcome_progress_written`), each flagged
|
|
11
|
+
* `ignorable`, and never in a field of an older record: a release that
|
|
12
|
+
* validates `turn_completed`, `child_session_ended` or the checkpoint strictly
|
|
13
|
+
* reads a log that has them and loses only the outcome. The fold of messages
|
|
14
|
+
* does not look at them. A body that does not parse as a `TurnOutcome` reads
|
|
15
|
+
* as no outcome, never as a guess.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** `value` as a {@link TurnOutcome}, or `undefined` when it is not one. */
|
|
19
|
+
export function parseTurnOutcome(value: unknown): TurnOutcome | undefined {
|
|
20
|
+
const parsed = TurnOutcomeSchema.safeParse(value)
|
|
21
|
+
return parsed.success ? parsed.data : undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The outcome of each turn that recorded one, from records in log order. A
|
|
26
|
+
* turn that recorded none is absent, so a caller never reads `unchanged` into
|
|
27
|
+
* a turn nobody measured.
|
|
28
|
+
*/
|
|
29
|
+
export function foldTurnOutcomes(records: Iterable<SessionRecord>): Map<TurnId, TurnOutcome> {
|
|
30
|
+
const outcomes = new Map<TurnId, TurnOutcome>()
|
|
31
|
+
for (const record of records) {
|
|
32
|
+
if (record.type !== 'turn_outcome_recorded') continue
|
|
33
|
+
const outcome = parseTurnOutcome(record.outcome)
|
|
34
|
+
if (outcome) outcomes.set(record.turnId, outcome)
|
|
35
|
+
else outcomes.delete(record.turnId)
|
|
36
|
+
}
|
|
37
|
+
return outcomes
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The outcome `turnId` recorded, or `undefined`. */
|
|
41
|
+
export async function readTurnOutcome(
|
|
42
|
+
log: SessionLog,
|
|
43
|
+
turnId: TurnId,
|
|
44
|
+
): Promise<TurnOutcome | undefined> {
|
|
45
|
+
let found: TurnOutcome | undefined
|
|
46
|
+
for await (const { record } of log.read({ mode: 'tolerant' })) {
|
|
47
|
+
if (record.type !== 'turn_outcome_recorded' || record.turnId !== turnId) continue
|
|
48
|
+
found = parseTurnOutcome(record.outcome)
|
|
49
|
+
}
|
|
50
|
+
return found
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The outcome a child's settled turn recorded in the parent's log, by child
|
|
55
|
+
* session id. A child with no record is absent.
|
|
56
|
+
*/
|
|
57
|
+
export function foldChildOutcomes(records: Iterable<SessionRecord>): Map<SessionId, TurnOutcome> {
|
|
58
|
+
const outcomes = new Map<SessionId, TurnOutcome>()
|
|
59
|
+
for (const record of records) {
|
|
60
|
+
if (record.type !== 'child_outcome_recorded') continue
|
|
61
|
+
const outcome = parseTurnOutcome(record.outcome)
|
|
62
|
+
if (outcome) outcomes.set(record.childSessionId, outcome)
|
|
63
|
+
else outcomes.delete(record.childSessionId)
|
|
64
|
+
}
|
|
65
|
+
return outcomes
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* What the outcome record had gathered when `checkpointId` was written, or
|
|
70
|
+
* `undefined` when no progress record names it (a checkpoint written before
|
|
71
|
+
* the record existed, or by a release that does not write it).
|
|
72
|
+
*/
|
|
73
|
+
export async function readOutcomeProgress(
|
|
74
|
+
log: SessionLog,
|
|
75
|
+
checkpointId: CheckpointId,
|
|
76
|
+
): Promise<Readonly<Record<string, unknown>> | undefined> {
|
|
77
|
+
let found: Readonly<Record<string, unknown>> | undefined
|
|
78
|
+
for await (const { record } of log.read({ mode: 'tolerant' })) {
|
|
79
|
+
if (record.type === 'outcome_progress_written' && record.checkpointId === checkpointId) {
|
|
80
|
+
found = record.progress
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return found
|
|
84
|
+
}
|
|
@@ -537,7 +537,7 @@ export const SANDBOX_ESCAPE_NOT_APPROVED =
|
|
|
537
537
|
'dangerously_disable_sandbox was not approved for this call, so the command did not run. Leaving the sandbox needs the user to confirm it each time, and this turn either cannot ask or does not allow it. Run the command inside the sandbox, or tell the user what it needs and let them run it.'
|
|
538
538
|
|
|
539
539
|
const BASH_DESCRIPTION =
|
|
540
|
-
'Executes a command line using the execution shell disclosed in the system context and returns stdout/stderr output. The working directory persists between calls, but shell state does not. Command timeout is configurable.
|
|
540
|
+
'Executes a command line using the execution shell disclosed in the system context and returns stdout/stderr output. The working directory persists between calls, but shell state does not. Command timeout is configurable. Read and search with `read`, `grep` and `glob`, not shell loops: a file read through the shell earns no read credit, so its next edit is refused once. Run a check on its own, not piped: its output is already trimmed, and a pipe hides the exit status.'
|
|
541
541
|
|
|
542
542
|
const BashToolBase = defineTool({
|
|
543
543
|
name: 'bash',
|
|
@@ -40,7 +40,7 @@ export type NormalizedEditInput =
|
|
|
40
40
|
export function normalizeEditInput(
|
|
41
41
|
input: EditInput,
|
|
42
42
|
): { success: true; operations: NormalizedEditInput[] } | { success: false; error: string } {
|
|
43
|
-
if (input.edits !== undefined) {
|
|
43
|
+
if (input.edits !== undefined && input.edits.length > 0) {
|
|
44
44
|
return {
|
|
45
45
|
success: true,
|
|
46
46
|
operations: input.edits.map((edit) => ({
|
|
@@ -197,11 +197,15 @@ function applyOne(
|
|
|
197
197
|
if (!content.includes(oldString)) {
|
|
198
198
|
const resolved = resolveOldString(content, oldString)
|
|
199
199
|
if (resolved === null) {
|
|
200
|
+
const near = nearLine(content, oldString)
|
|
200
201
|
return {
|
|
201
202
|
success: false,
|
|
202
203
|
reason: 'text_not_found',
|
|
203
|
-
error:
|
|
204
|
-
|
|
204
|
+
error: `old_string/oldStr not found in file. Make sure the string matches exactly, including whitespace and indentation. ${
|
|
205
|
+
near === undefined
|
|
206
|
+
? 'Read the lines you mean to change again and copy them as they are.'
|
|
207
|
+
: `A line that matches its first line apart from spacing is at line ${near}; read around it again and copy it as it is.`
|
|
208
|
+
}`,
|
|
205
209
|
}
|
|
206
210
|
}
|
|
207
211
|
newString = reconcileNewString(oldString, resolved, newString)
|
|
@@ -235,7 +239,7 @@ function applyOne(
|
|
|
235
239
|
return {
|
|
236
240
|
success: false,
|
|
237
241
|
reason: 'not_unique',
|
|
238
|
-
error: `old_string/oldStr is not unique — found at lines ${lineNumber} and ${secondLine}. Provide more surrounding context to make it unique, or use replace_all: true.`,
|
|
242
|
+
error: `old_string/oldStr is not unique — found at lines ${lineNumber} and ${secondLine}. Provide more surrounding context to make it unique, or use replace_all: true. Read around those lines again to choose context that is only in one of them.`,
|
|
239
243
|
}
|
|
240
244
|
}
|
|
241
245
|
|
|
@@ -247,6 +251,24 @@ function applyOne(
|
|
|
247
251
|
}
|
|
248
252
|
}
|
|
249
253
|
|
|
254
|
+
const NEAR_LINE_MIN_CHARS = 8
|
|
255
|
+
|
|
256
|
+
/** The line of `content` that matches the first non-blank line of `needle` apart from spacing, 1-based. */
|
|
257
|
+
function nearLine(content: string, needle: string): number | undefined {
|
|
258
|
+
const squash = (text: string) => text.replace(/\s+/g, ' ').trim()
|
|
259
|
+
const first = needle
|
|
260
|
+
.split('\n')
|
|
261
|
+
.map(squash)
|
|
262
|
+
.find((line) => line !== '')
|
|
263
|
+
// A bare brace or paren is on many lines; naming the first one would point at the wrong place.
|
|
264
|
+
if (first === undefined || first.length < NEAR_LINE_MIN_CHARS) return undefined
|
|
265
|
+
const lines = content.split('\n')
|
|
266
|
+
for (let i = 0; i < lines.length; i++) {
|
|
267
|
+
if (squash(lines[i] ?? '') === first) return i + 1
|
|
268
|
+
}
|
|
269
|
+
return undefined
|
|
270
|
+
}
|
|
271
|
+
|
|
250
272
|
function applyLineInsert(
|
|
251
273
|
content: string,
|
|
252
274
|
input: Extract<NormalizedEditInput, { operation: 'insert' }>,
|