@xingular/harness 2.0.0 → 2.1.1

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.
@@ -1,12 +1,2 @@
1
- import { ASK_USER } from '../../tools/askUser'
2
-
3
- // Read-only inspection, questions and recall. Everything else is withdrawn in plan mode: file changes, commands,
4
- // memory writes, delegation and connected apps, since none of them can be judged read-only from their names.
5
- const READ_ONLY_TOOLS = new Set([
6
- 'read', 'grep', 'find', 'ls', ASK_USER, 'ask_user_question',
7
- 'history_search', 'history_read', 'wiki_entrypoint', 'wiki_search', 'wiki_read', 'wiki_list', 'memory_facts'
8
- ])
9
-
10
- export function planModeTools(normal: readonly string[]): string[] {
11
- return normal.filter(name => READ_ONLY_TOOLS.has(name))
12
- }
1
+ // The terminal's plan mode uses the session's read-only tool set.
2
+ export { planModeTools } from '../../sessions/planMode'
@@ -7,7 +7,8 @@ import {
7
7
  import { closeSync, existsSync, openSync, readSync, realpathSync } from 'node:fs'
8
8
  import { isAbsolute, join, relative, resolve, sep } from 'node:path'
9
9
 
10
- import terminalExtension from '../piExtension'
10
+ import terminalExtension, { type TerminalPlanRunState } from '../piExtension'
11
+ import { planModeTools } from './planMode'
11
12
  import { claimTerminalControl, controlCall } from '../control'
12
13
  import { loadPiAdapter } from './piAdapter'
13
14
  import type { SessionAccount } from '../accountsMenu'
@@ -101,6 +102,7 @@ export interface TerminalRuntime {
101
102
  answerBatch(batchId: string, answers: Array<{ questionId: string; answer: QuestionAnswer }>): Promise<void>
102
103
  snapshot(): TerminalSnapshot
103
104
  submit(text: string, followUp?: boolean): Promise<void>
105
+ submitPlan(text: string): Promise<void>
104
106
  readonly session: AgentSession
105
107
  readonly services: AgentSessionServices
106
108
  readonly cwd: string
@@ -200,6 +202,7 @@ export async function createTerminalRuntime(args: string[], host: TerminalRuntim
200
202
  const generation = uiGeneration
201
203
  return sessionUiContext(host.ui, () => !disposed && generation === uiGeneration)
202
204
  }
205
+ const planRun: TerminalPlanRunState = { active: false, allowedTools: new Set() }
203
206
 
204
207
  const factory: CreateAgentSessionRuntimeFactory = async ({ cwd: targetCwd, sessionManager, sessionStartEvent }) => {
205
208
  if (realpathSync(targetCwd) !== cwd) throw new Error('A native session cannot change this terminal working folder.')
@@ -213,7 +216,7 @@ export async function createTerminalRuntime(args: string[], host: TerminalRuntim
213
216
  additionalExtensionPaths: prepared.extensions.slice(0, -1),
214
217
  additionalSkillPaths: prepared.skills, additionalPromptTemplatePaths: prepared.prompts,
215
218
  additionalThemePaths: prepared.themes,
216
- extensionFactories: [...adapter.builtins, { name: 'harness-terminal-bridge', factory: terminalExtension, hidden: true }],
219
+ extensionFactories: [...adapter.builtins, { name: 'harness-terminal-bridge', factory: pi => terminalExtension(pi, planRun), hidden: true }],
217
220
  appendSystemPromptOverride: (base) => [...base, ...prepared.appendSystemPrompt]
218
221
  })
219
222
  const reloadResources = resourceLoader.reload.bind(resourceLoader)
@@ -309,6 +312,25 @@ export async function createTerminalRuntime(args: string[], host: TerminalRuntim
309
312
  const input = text.trimStart().startsWith('/') ? text.trimStart() : text
310
313
  await native.session.prompt(input, native.session.isStreaming ? { streamingBehavior: followUp ? 'followUp' : 'steer' } : {})
311
314
  },
315
+ async submitPlan(text) {
316
+ if (disposed) throw new Error('The terminal runtime has closed.')
317
+ if (changing) throw new Error('Wait for the native session change to finish before submitting.')
318
+ if (!text.trim()) throw new Error('Use /plan <plan brief>.')
319
+ const session = native.session
320
+ if (!session.isIdle) throw new Error('A plan request must wait until the current response settles.')
321
+ const originalTools = session.getActiveToolNames()
322
+ const allowedTools = planModeTools(originalTools)
323
+ planRun.allowedTools = new Set(allowedTools)
324
+ planRun.active = true
325
+ try {
326
+ session.setActiveToolsByName(allowedTools)
327
+ await session.prompt(text, { expandPromptTemplates: false })
328
+ } finally {
329
+ planRun.active = false
330
+ planRun.allowedTools = new Set()
331
+ if (!disposed && native.session === session) session.setActiveToolsByName(originalTools)
332
+ }
333
+ },
312
334
  get session() { return native.session },
313
335
  get services() { return native.services },
314
336
  cwd, sessionDirectory,
@@ -20,6 +20,8 @@ export interface ToolSpec {
20
20
  description: string
21
21
  /** Builds the TypeBox parameter schema. */
22
22
  parameters: (type: typeof import('typebox').Type) => unknown
23
+ /** Only reads or asks; such a tool stays callable in plan mode. */
24
+ readOnly?: boolean
23
25
  execute: (input: Record<string, unknown>) => Promise<string | ToolResult>
24
26
  }
25
27