@te-river/opencode-team-mode 1.6.1 → 1.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/README.md +117 -118
- package/README.zh-CN.md +98 -100
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +89 -79
- package/dist/agents.js.map +1 -1
- package/dist/approval-gate.d.ts +8 -9
- package/dist/approval-gate.d.ts.map +1 -1
- package/dist/approval-gate.js +13 -14
- package/dist/approval-gate.js.map +1 -1
- package/dist/capabilities.d.ts +11 -37
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +11 -141
- package/dist/capabilities.js.map +1 -1
- package/dist/commands.js +1 -1
- package/dist/envprotect/hook.d.ts +12 -31
- package/dist/envprotect/hook.d.ts.map +1 -1
- package/dist/envprotect/hook.js +13 -91
- package/dist/envprotect/hook.js.map +1 -1
- package/dist/envprotect/patterns.d.ts +6 -4
- package/dist/envprotect/patterns.d.ts.map +1 -1
- package/dist/envprotect/patterns.js +6 -4
- package/dist/envprotect/patterns.js.map +1 -1
- package/dist/envprotect.d.ts +1 -1
- package/dist/envprotect.d.ts.map +1 -1
- package/dist/envprotect.js +1 -1
- package/dist/envprotect.js.map +1 -1
- package/dist/host/note.d.ts.map +1 -1
- package/dist/host/note.js +8 -5
- package/dist/host/note.js.map +1 -1
- package/dist/host/v2-browser-gate.d.ts +19 -1
- package/dist/host/v2-browser-gate.d.ts.map +1 -1
- package/dist/host/v2-browser-gate.js +47 -6
- package/dist/host/v2-browser-gate.js.map +1 -1
- package/dist/host/v2-capabilities.d.ts +43 -0
- package/dist/host/v2-capabilities.d.ts.map +1 -1
- package/dist/host/v2-capabilities.js +77 -1
- package/dist/host/v2-capabilities.js.map +1 -1
- package/dist/host/v2-compaction.d.ts +129 -0
- package/dist/host/v2-compaction.d.ts.map +1 -0
- package/dist/host/v2-compaction.js +346 -0
- package/dist/host/v2-compaction.js.map +1 -0
- package/dist/host/v2-default-agent.d.ts +37 -0
- package/dist/host/v2-default-agent.d.ts.map +1 -0
- package/dist/host/v2-default-agent.js +128 -0
- package/dist/host/v2-default-agent.js.map +1 -0
- package/dist/host/v2-events.d.ts +2 -0
- package/dist/host/v2-events.d.ts.map +1 -1
- package/dist/host/v2-events.js +27 -0
- package/dist/host/v2-events.js.map +1 -1
- package/dist/host/v2-guard.d.ts +178 -0
- package/dist/host/v2-guard.d.ts.map +1 -1
- package/dist/host/v2-guard.js +324 -8
- package/dist/host/v2-guard.js.map +1 -1
- package/dist/host/v2-offload.d.ts +1 -1
- package/dist/host/v2-offload.js +2 -2
- package/dist/host/v2-permissions.d.ts +45 -44
- package/dist/host/v2-permissions.d.ts.map +1 -1
- package/dist/host/v2-permissions.js +48 -49
- package/dist/host/v2-permissions.js.map +1 -1
- package/dist/host/v2-probe-chain.d.ts +93 -0
- package/dist/host/v2-probe-chain.d.ts.map +1 -0
- package/dist/host/v2-probe-chain.js +146 -0
- package/dist/host/v2-probe-chain.js.map +1 -0
- package/dist/host/v2-prune.d.ts +157 -0
- package/dist/host/v2-prune.d.ts.map +1 -0
- package/dist/host/v2-prune.js +507 -0
- package/dist/host/v2-prune.js.map +1 -0
- package/dist/host/v2-retry.d.ts +143 -0
- package/dist/host/v2-retry.d.ts.map +1 -0
- package/dist/host/v2-retry.js +315 -0
- package/dist/host/v2-retry.js.map +1 -0
- package/dist/host/v2-scope.d.ts.map +1 -1
- package/dist/host/v2-scope.js +14 -7
- package/dist/host/v2-scope.js.map +1 -1
- package/dist/host/v2-session-client.d.ts +62 -7
- package/dist/host/v2-session-client.d.ts.map +1 -1
- package/dist/host/v2-session-client.js +485 -26
- package/dist/host/v2-session-client.js.map +1 -1
- package/dist/host/v2-session.d.ts +72 -1
- package/dist/host/v2-session.d.ts.map +1 -1
- package/dist/host/v2-session.js +154 -10
- package/dist/host/v2-session.js.map +1 -1
- package/dist/host/v2-tool.d.ts +2 -3
- package/dist/host/v2-tool.d.ts.map +1 -1
- package/dist/host/v2-tool.js +2 -3
- package/dist/host/v2-tool.js.map +1 -1
- package/dist/host/v2-types.d.ts +25 -0
- package/dist/host/v2-types.d.ts.map +1 -1
- package/dist/host/v2.d.ts +14 -12
- package/dist/host/v2.d.ts.map +1 -1
- package/dist/host/v2.js +648 -74
- package/dist/host/v2.js.map +1 -1
- package/dist/identity.d.ts +27 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +34 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +9 -24
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -27
- package/dist/index.js.map +1 -1
- package/dist/prompts/lead.d.ts +1 -1
- package/dist/prompts/lead.d.ts.map +1 -1
- package/dist/prompts/lead.js +44 -27
- package/dist/prompts/lead.js.map +1 -1
- package/dist/prompts/shared.d.ts +2 -2
- package/dist/prompts/shared.d.ts.map +1 -1
- package/dist/prompts/shared.js +74 -57
- package/dist/prompts/shared.js.map +1 -1
- package/dist/prompts/specialists.d.ts +2 -2
- package/dist/prompts/specialists.d.ts.map +1 -1
- package/dist/prompts/specialists.js +53 -83
- package/dist/prompts/specialists.js.map +1 -1
- package/dist/tm/args-schema.d.ts +7 -32
- package/dist/tm/args-schema.d.ts.map +1 -1
- package/dist/tm/args-schema.js +12 -153
- package/dist/tm/args-schema.js.map +1 -1
- package/dist/tm/board.d.ts +10 -9
- package/dist/tm/board.d.ts.map +1 -1
- package/dist/tm/board.js +11 -10
- package/dist/tm/board.js.map +1 -1
- package/dist/tm/config-files.d.ts +81 -0
- package/dist/tm/config-files.d.ts.map +1 -0
- package/dist/tm/config-files.js +209 -0
- package/dist/tm/config-files.js.map +1 -0
- package/dist/tm/config-layers.d.ts +117 -0
- package/dist/tm/config-layers.d.ts.map +1 -0
- package/dist/tm/config-layers.js +320 -0
- package/dist/tm/config-layers.js.map +1 -0
- package/dist/tm/config.d.ts +10 -101
- package/dist/tm/config.d.ts.map +1 -1
- package/dist/tm/config.js +17 -71
- package/dist/tm/config.js.map +1 -1
- package/dist/tm/dispatch.d.ts +154 -22
- package/dist/tm/dispatch.d.ts.map +1 -1
- package/dist/tm/dispatch.js +559 -80
- package/dist/tm/dispatch.js.map +1 -1
- package/dist/tm/egress.d.ts +19 -7
- package/dist/tm/egress.d.ts.map +1 -1
- package/dist/tm/egress.js +21 -9
- package/dist/tm/egress.js.map +1 -1
- package/dist/tm/index.d.ts +23 -20
- package/dist/tm/index.d.ts.map +1 -1
- package/dist/tm/index.js +117 -116
- package/dist/tm/index.js.map +1 -1
- package/dist/tm/ledger.d.ts +9 -2
- package/dist/tm/ledger.d.ts.map +1 -1
- package/dist/tm/ledger.js +4 -1
- package/dist/tm/ledger.js.map +1 -1
- package/dist/tm/perm-ask.d.ts +3 -3
- package/dist/tm/perm-ask.d.ts.map +1 -1
- package/dist/tm/perm-ask.js +3 -3
- package/dist/tm/perm-ask.js.map +1 -1
- package/dist/tm/pipelines.d.ts +12 -11
- package/dist/tm/pipelines.d.ts.map +1 -1
- package/dist/tm/pipelines.js +40 -207
- package/dist/tm/pipelines.js.map +1 -1
- package/dist/tm/preview.d.ts +1 -1
- package/dist/tm/preview.js +3 -3
- package/dist/tm/preview.js.map +1 -1
- package/dist/tm/refs.d.ts +31 -4
- package/dist/tm/refs.d.ts.map +1 -1
- package/dist/tm/refs.js +48 -4
- package/dist/tm/refs.js.map +1 -1
- package/dist/tm/result.d.ts +1 -1
- package/dist/tm/result.d.ts.map +1 -1
- package/dist/tm/result.js +4 -3
- package/dist/tm/result.js.map +1 -1
- package/dist/tm/search.d.ts +2 -2
- package/dist/tm/search.js +7 -7
- package/dist/tm/search.js.map +1 -1
- package/dist/tm/serp-loop.d.ts +19 -7
- package/dist/tm/serp-loop.d.ts.map +1 -1
- package/dist/tm/serp-loop.js +24 -9
- package/dist/tm/serp-loop.js.map +1 -1
- package/dist/tm/stats.d.ts +31 -27
- package/dist/tm/stats.d.ts.map +1 -1
- package/dist/tm/stats.js +170 -58
- package/dist/tm/stats.js.map +1 -1
- package/dist/tm/store.d.ts +18 -5
- package/dist/tm/store.d.ts.map +1 -1
- package/dist/tm/store.js +39 -9
- package/dist/tm/store.js.map +1 -1
- package/dist/tm/tools.d.ts +7 -5
- package/dist/tm/tools.d.ts.map +1 -1
- package/dist/tm/tools.js +10 -41
- package/dist/tm/tools.js.map +1 -1
- package/dist/tm/webfetch.d.ts +1 -1
- package/dist/tm/webfetch.d.ts.map +1 -1
- package/dist/tm/webfetch.js +38 -23
- package/dist/tm/webfetch.js.map +1 -1
- package/dist/types.d.ts +40 -123
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -21
- package/dist/types.js.map +1 -1
- package/docs/installation-v2.md +124 -39
- package/docs/installation.md +9 -0
- package/docs/research/browser-pane.md +5 -0
- package/docs/v2-port-notes.md +500 -315
- package/package.json +1 -1
- package/scripts/gen-v2-config.mjs +246 -94
- package/scripts/lib/fake-ctx.mjs +244 -146
package/scripts/lib/fake-ctx.mjs
CHANGED
|
@@ -1,146 +1,244 @@
|
|
|
1
|
-
// A fake OpenCode v2 plugin context for the adapter tests.
|
|
2
|
-
//
|
|
3
|
-
// There was no such thing before the v2 port: every existing suite builds a v1
|
|
4
|
-
// tool ctx as an inline literal ({directory, sessionID, agent, ask}), because
|
|
5
|
-
// v1 hands tools a plain object. v2 hands the PLUGIN a context of domains with
|
|
6
|
-
// transform()/hook() registrars, so testing the v2 personality needs a host
|
|
7
|
-
// double that records what was registered and can replay a hook.
|
|
8
|
-
//
|
|
9
|
-
// Deliberately faithful on the three points that matter:
|
|
10
|
-
// - transform(cb) runs the callback SYNCHRONOUSLY against a live editor and
|
|
11
|
-
// returns a disposable (a fake that defers would let a plugin pass tests
|
|
12
|
-
// against an API shape the host does not have);
|
|
13
|
-
// - hook callbacks are callable by the test so a hook body can be exercised
|
|
14
|
-
// with a real payload;
|
|
15
|
-
// - nothing here touches the filesystem or the network.
|
|
16
|
-
|
|
17
|
-
export function makeFakeCtx({
|
|
18
|
-
directory = process.cwd(),
|
|
19
|
-
options = {},
|
|
20
|
-
agents: seedAgents = [],
|
|
21
|
-
tools: seedTools = [],
|
|
22
|
-
sessionData = null,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
async
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
async
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
1
|
+
// A fake OpenCode v2 plugin context for the adapter tests.
|
|
2
|
+
//
|
|
3
|
+
// There was no such thing before the v2 port: every existing suite builds a v1
|
|
4
|
+
// tool ctx as an inline literal ({directory, sessionID, agent, ask}), because
|
|
5
|
+
// v1 hands tools a plain object. v2 hands the PLUGIN a context of domains with
|
|
6
|
+
// transform()/hook() registrars, so testing the v2 personality needs a host
|
|
7
|
+
// double that records what was registered and can replay a hook.
|
|
8
|
+
//
|
|
9
|
+
// Deliberately faithful on the three points that matter:
|
|
10
|
+
// - transform(cb) runs the callback SYNCHRONOUSLY against a live editor and
|
|
11
|
+
// returns a disposable (a fake that defers would let a plugin pass tests
|
|
12
|
+
// against an API shape the host does not have);
|
|
13
|
+
// - hook callbacks are callable by the test so a hook body can be exercised
|
|
14
|
+
// with a real payload;
|
|
15
|
+
// - nothing here touches the filesystem or the network.
|
|
16
|
+
|
|
17
|
+
export function makeFakeCtx({
|
|
18
|
+
directory = process.cwd(),
|
|
19
|
+
options = {},
|
|
20
|
+
agents: seedAgents = [],
|
|
21
|
+
tools: seedTools = [],
|
|
22
|
+
sessionData = null,
|
|
23
|
+
models: seedModels = null,
|
|
24
|
+
} = {}) {
|
|
25
|
+
const registrations = []
|
|
26
|
+
const hooks = new Map()
|
|
27
|
+
|
|
28
|
+
const disposable = (label) => {
|
|
29
|
+
const rec = { label, disposed: false, dispose: async () => { rec.disposed = true } }
|
|
30
|
+
registrations.push(rec)
|
|
31
|
+
return rec
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const toolMap = new Map()
|
|
35
|
+
for (const t of seedTools) toolMap.set(t.name ?? t.id, { ...t, id: t.id ?? t.name })
|
|
36
|
+
const toolEditor = {
|
|
37
|
+
list: () => [...toolMap.values()],
|
|
38
|
+
get: (id) => toolMap.get(id),
|
|
39
|
+
add: (tool) => { toolMap.set(tool.name, { ...tool, id: tool.name }) },
|
|
40
|
+
update: (id, fn) => { const cur = toolMap.get(id); if (cur) fn(cur) },
|
|
41
|
+
remove: (id) => { toolMap.delete(id) },
|
|
42
|
+
namespace: () => {},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const agentMap = new Map()
|
|
46
|
+
for (const a of seedAgents) agentMap.set(a.id, { ...a })
|
|
47
|
+
const agentEditor = {
|
|
48
|
+
list: () => [...agentMap.values()],
|
|
49
|
+
get: (id) => agentMap.get(id),
|
|
50
|
+
default: (id) => { agentEditor.__default = id },
|
|
51
|
+
update: (id, fn) => { const cur = agentMap.get(id); if (cur) fn(cur) },
|
|
52
|
+
remove: (id) => { agentMap.delete(id) },
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const hookHandle = (domain, name) => {
|
|
56
|
+
const key = `${domain}.${name}`
|
|
57
|
+
const entry = hooks.get(key) ?? { key, calls: [], fire: async (input) => {
|
|
58
|
+
for (const cb of entry.handlers) await cb(input)
|
|
59
|
+
return input
|
|
60
|
+
}, handlers: [] }
|
|
61
|
+
hooks.set(key, entry)
|
|
62
|
+
return entry
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const mkHook = (domain) => async (name, callback) => {
|
|
66
|
+
hookHandle(domain, name).handlers.push(callback)
|
|
67
|
+
return disposable(`${domain}.hook(${name})`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const warnLines = []
|
|
71
|
+
const errorLines = []
|
|
72
|
+
/** #33: which child sessions a stop attempt actually reached, in order. */
|
|
73
|
+
const stopCalls = []
|
|
74
|
+
/** #steer: what each interjection seam actually received, args and all. A test that
|
|
75
|
+
* only read the tool's prose could not tell 插话给了子会话 from 插话给了调用者自己,
|
|
76
|
+
* and that distinction is the whole safety property of the feature. */
|
|
77
|
+
const promptCalls = []
|
|
78
|
+
const syntheticCalls = []
|
|
79
|
+
const inboxListCalls = []
|
|
80
|
+
const inboxCancelCalls = []
|
|
81
|
+
/** #39: the compaction admissions the fake actually received, and how many times the
|
|
82
|
+
* catalog was listed. A test that only read the layer's own counters could not tell
|
|
83
|
+
* "we asked the host to compact" from "we decided to and told nobody". */
|
|
84
|
+
const compactCalls = []
|
|
85
|
+
const modelListCalls = []
|
|
86
|
+
|
|
87
|
+
/** Turn a seeded verdict into a host-shaped method: a function is called with the args
|
|
88
|
+
* (so a test can answer per-session), an Error throws, anything else is returned as-is.
|
|
89
|
+
* Same construction as `stop` above — a fake that always answered `true` would let the
|
|
90
|
+
* tool claim 已受理 against a host that never admitted anything. */
|
|
91
|
+
const verdictMethod = (seed, logArr) => async (args = {}) => {
|
|
92
|
+
logArr.push({ ...(args ?? {}) })
|
|
93
|
+
const v = typeof seed === "function" ? seed(args) : seed
|
|
94
|
+
if (v instanceof Error) throw v
|
|
95
|
+
return v
|
|
96
|
+
}
|
|
97
|
+
const has = (key) => sessionData && Object.prototype.hasOwnProperty.call(sessionData, key)
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
ctx: {
|
|
101
|
+
location: { directory, project: { directory } },
|
|
102
|
+
options,
|
|
103
|
+
tool: {
|
|
104
|
+
transform: async (cb) => { cb(toolEditor); return disposable("tool.transform") },
|
|
105
|
+
list: async () => [...toolMap.values()],
|
|
106
|
+
hook: mkHook("tool"),
|
|
107
|
+
reload: async () => {},
|
|
108
|
+
},
|
|
109
|
+
agent: {
|
|
110
|
+
transform: async (cb) => { cb(agentEditor); return disposable("agent.transform") },
|
|
111
|
+
list: async () => [...agentMap.values()],
|
|
112
|
+
},
|
|
113
|
+
permission: { hook: mkHook("permission"), list: async () => [], get: async () => ({}), reply: async () => ({}) },
|
|
114
|
+
// `session.get` / `session.context` exist ONLY when a test seeds them, because a fake
|
|
115
|
+
// that always answers would let a plugin pass while the real host refuses — and the
|
|
116
|
+
// shapes below are the ones measured on 2.0.18 (`get` → {id,parentID,…}, `context` →
|
|
117
|
+
// an array of {id,time,text,type}), not a guess. See src/host/v2-transcript.ts.
|
|
118
|
+
//
|
|
119
|
+
// `session.interrupt` (#33) is seeded through `stop`, so a test can hand back each of
|
|
120
|
+
// the host's documented answers separately — `{interrupted:true}` (an active execution
|
|
121
|
+
// was stopped), `{interrupted:false}` (the idle no-op), no boolean at all, or a throw.
|
|
122
|
+
// A fake that always said `true` would let the tool claim 已停止 against a host that
|
|
123
|
+
// never stops anything, which is the exact overstatement the real code refuses.
|
|
124
|
+
session: sessionData
|
|
125
|
+
? {
|
|
126
|
+
hook: mkHook("session"),
|
|
127
|
+
async get({ sessionID } = {}) {
|
|
128
|
+
const s = sessionData.sessions?.[sessionID]
|
|
129
|
+
if (!s) throw new Error(`Session not found: ${sessionID}`)
|
|
130
|
+
return { id: sessionID, ...s }
|
|
131
|
+
},
|
|
132
|
+
async context({ sessionID } = {}) {
|
|
133
|
+
return sessionData.messages?.[sessionID] ?? []
|
|
134
|
+
},
|
|
135
|
+
...(sessionData.stop
|
|
136
|
+
? {
|
|
137
|
+
async interrupt({ sessionID } = {}) {
|
|
138
|
+
stopCalls.push({ sessionID })
|
|
139
|
+
const verdict = typeof sessionData.stop === "function"
|
|
140
|
+
? sessionData.stop(sessionID)
|
|
141
|
+
: sessionData.stop
|
|
142
|
+
if (verdict instanceof Error) throw verdict
|
|
143
|
+
return verdict
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
: {}),
|
|
147
|
+
// #steer — the interjection seams, seeded ONLY when a test asks for them, for
|
|
148
|
+
// the same reason `interrupt` is: a host that never admits inbox input must not
|
|
149
|
+
// be able to be described as having accepted one. `prompt` is the documented
|
|
150
|
+
// `{sessionID, text, delivery}` call; `synthetic` is the fallback seam; the
|
|
151
|
+
// inbox ops exist in TWO spellings (nested `session.inbox.list`, which the
|
|
152
|
+
// published operation ids imply, and flat `session["inbox.list"]`), and
|
|
153
|
+
// `inboxFlat` lets a test drive the fallback and pin which one answered.
|
|
154
|
+
...(has("prompt") ? { prompt: verdictMethod(sessionData.prompt, promptCalls) } : {}),
|
|
155
|
+
// #39 — `compact` is seeded ONLY when a test asks for it, for the same reason
|
|
156
|
+
// as `interrupt`: the 2.0.23 probe proved the seam exists (`compact({})` →
|
|
157
|
+
// Missing key at ["sessionID"]), but a fake that always had it would let the
|
|
158
|
+
// layer claim a host that has none.
|
|
159
|
+
...(has("compact") ? { compact: verdictMethod(sessionData.compact, compactCalls) } : {}),
|
|
160
|
+
...(has("synthetic") ? { synthetic: verdictMethod(sessionData.synthetic, syntheticCalls) } : {}),
|
|
161
|
+
...(has("inbox") && sessionData.inbox && !sessionData.inboxFlat
|
|
162
|
+
? {
|
|
163
|
+
inbox: {
|
|
164
|
+
...(sessionData.inbox.list !== undefined
|
|
165
|
+
? { list: verdictMethod(sessionData.inbox.list, inboxListCalls) }
|
|
166
|
+
: {}),
|
|
167
|
+
...(sessionData.inbox.cancel !== undefined
|
|
168
|
+
? { cancel: verdictMethod(sessionData.inbox.cancel, inboxCancelCalls) }
|
|
169
|
+
: {}),
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
: {}),
|
|
173
|
+
...(has("inbox") && sessionData.inbox && sessionData.inboxFlat
|
|
174
|
+
? {
|
|
175
|
+
"inbox.list": verdictMethod(sessionData.inbox.list, inboxListCalls),
|
|
176
|
+
"inbox.cancel": verdictMethod(sessionData.inbox.cancel, inboxCancelCalls),
|
|
177
|
+
}
|
|
178
|
+
: {}),
|
|
179
|
+
}
|
|
180
|
+
: { hook: mkHook("session") },
|
|
181
|
+
shell: { hook: mkHook("shell") },
|
|
182
|
+
// `model.list()` measured on 2.0.23: `{location, data:[{id, modelID, providerID,
|
|
183
|
+
// …, limit:{context, output}}]}`. The shape below is that shape, because the early
|
|
184
|
+
// compaction layer reads `limit.context` as the denominator and a fake with a
|
|
185
|
+
// friendlier shape would test the reading, not the contract.
|
|
186
|
+
model: {
|
|
187
|
+
async list(opts = {}) {
|
|
188
|
+
modelListCalls.push({ ...(opts ?? {}) })
|
|
189
|
+
return { location: { directory }, data: seedModels ?? [] }
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
storage: {
|
|
193
|
+
_map: new Map(),
|
|
194
|
+
async get(k) { return this._map.get(k) },
|
|
195
|
+
async set(k, v) { this._map.set(k, v) },
|
|
196
|
+
async remove(k) { this._map.delete(k) },
|
|
197
|
+
async scan() { return { entries: [] } },
|
|
198
|
+
},
|
|
199
|
+
event: { subscribe: () => ({ async [Symbol.asyncIterator]() { return { done: true, value: undefined } } }) },
|
|
200
|
+
app: { name: "opencode-fake", version: "2.0.16", channel: "test" },
|
|
201
|
+
},
|
|
202
|
+
/** Introspection for assertions. */
|
|
203
|
+
registrations,
|
|
204
|
+
tools: toolEditor,
|
|
205
|
+
agents: agentEditor,
|
|
206
|
+
/** Introspection for assertions. Only the FIRST dot separates the domain
|
|
207
|
+
* from the hook name — `tool.execute.before` is domain "tool" + hook
|
|
208
|
+
* "execute.before", and splitting on every dot would look up a key that
|
|
209
|
+
* nothing ever registered, so a working hook would read as an absent one. */
|
|
210
|
+
hook: (name) => {
|
|
211
|
+
const [domain, ...rest] = name.split(".")
|
|
212
|
+
return hookHandle(domain, rest.join("."))
|
|
213
|
+
},
|
|
214
|
+
hookNames: () => [...hooks.keys()],
|
|
215
|
+
consoleWarn: warnLines,
|
|
216
|
+
consoleError: errorLines,
|
|
217
|
+
/** #33 — the stop attempts this fake actually received, so a test can pin that a
|
|
218
|
+
* cancel reached the host with the child's id rather than trusting the tool's prose. */
|
|
219
|
+
stopCalls,
|
|
220
|
+
/** #steer — the same for the interjection seams: the args each one really got. */
|
|
221
|
+
promptCalls,
|
|
222
|
+
syntheticCalls,
|
|
223
|
+
inboxListCalls,
|
|
224
|
+
inboxCancelCalls,
|
|
225
|
+
/** #39 — the compaction admissions and the catalog reads, with their args. */
|
|
226
|
+
compactCalls,
|
|
227
|
+
modelListCalls,
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Capture what the personality logs during setup — a v2 plugin has no toast,
|
|
232
|
+
* so the server log IS the user-visible channel for these gaps. */
|
|
233
|
+
export function withCapturedConsole(fn) {
|
|
234
|
+
const warns = []
|
|
235
|
+
const errors = []
|
|
236
|
+
const w = console.warn
|
|
237
|
+
const e = console.error
|
|
238
|
+
console.warn = (...a) => { warns.push(a.join(" ")) }
|
|
239
|
+
console.error = (...a) => { errors.push(a.join(" ")) }
|
|
240
|
+
return Promise.resolve()
|
|
241
|
+
.then(fn)
|
|
242
|
+
.finally(() => { console.warn = w; console.error = e })
|
|
243
|
+
.then((value) => ({ value, warns, errors }))
|
|
244
|
+
}
|