mirror of
https://github.com/microsoft/vscode.git
synced 2026-08-22 13:13:08 +01:00
Add agent session orchestration fan-in (#331518)
This commit is contained in:
@@ -227,6 +227,32 @@ Provider-private discovery helpers name their concrete source: Claude uses `_lis
|
||||
|
||||
For every provider, migration and discovery partition the same native catalog: migration returns known entries as plain metadata, while discovery emits unknown entries with provider-classified provenance (external for Claude and Codex, and for Copilot everything except an unknown legacy extension-host chat, which is emitted as internal and adoptable). The partition is not quite exhaustive for Copilot: a chat whose session database exists but holds none of the metadata keys `listChatsToMigrate` requires is rejected by both halves. That is deliberate — an empty database is how Agent Host records a chat it already touched — and is asserted by `copilotAgent.test.ts`'s "does not discover an extension-host chat with an empty Agent Host database". Central `agent-host.db` remains the durable provenance authority.
|
||||
|
||||
### Server-tool orchestration relationships
|
||||
|
||||
Treat a session as the user-visible unit of work. The `create_chat` tool is the
|
||||
default for parallel subtasks that should share one workspace, lifecycle, and
|
||||
aggregate diff. Use `create_session` only when a delegated task needs an
|
||||
independent workspace, worktree or branch, provider, or lifecycle.
|
||||
|
||||
Sessions created by the `create_session` server tool record provider-neutral
|
||||
orchestration metadata in the session summary `_meta` bag. The metadata names
|
||||
the creating session separately from the hierarchy parent, plus an optional
|
||||
label, whether the child may coordinate with its creator, and an optional
|
||||
idle-notification policy. Keeping creator identity separate from hierarchy
|
||||
placement preserves notification routing if parent relationships evolve.
|
||||
`list_sessions` projects and filters hierarchy metadata without involving
|
||||
provider harnesses.
|
||||
|
||||
`SessionCoordinationService` owns idle-notification status observation,
|
||||
per-child sequencing, creator restoration, and delivery. Its durable
|
||||
`creatorNotificationState` is `waitingForCompletion` after work starts and
|
||||
`notified` after the next input-needed/idle/error transition wakes the creator.
|
||||
The `always` policy returns to `waitingForCompletion` on the next work cycle. A
|
||||
busy creator default chat receives a queued system notification rather than a
|
||||
new active turn, so concurrent child completion cannot overwrite creator work.
|
||||
The existing pending-message drain starts that queued notification when the
|
||||
creator chat becomes idle.
|
||||
|
||||
`list_sessions` exposes a session's configured project URI separately from its
|
||||
primary and additional working directories. `create_session` accepts those URIs
|
||||
directly and can resolve a unique project display name, preferring the
|
||||
|
||||
@@ -1711,6 +1711,62 @@ export function withSessionSpawnDepth(meta: SessionSummaryMeta | undefined, dept
|
||||
return { ...meta, [SESSION_META_SPAWN_DEPTH_KEY]: depth };
|
||||
}
|
||||
|
||||
export type SessionIdleNotification = 'once' | 'always';
|
||||
export type SessionCreatorNotificationState = 'waitingForCompletion' | 'notified';
|
||||
|
||||
export interface ISessionOrchestration {
|
||||
readonly parentSession: string;
|
||||
readonly creatorSession: string;
|
||||
readonly label?: string;
|
||||
readonly coordinateWithCreator: boolean;
|
||||
readonly notifyOnIdle?: SessionIdleNotification;
|
||||
/** Durable delivery state used to wait for a work outcome and deduplicate replayed statuses. */
|
||||
readonly creatorNotificationState?: SessionCreatorNotificationState;
|
||||
}
|
||||
|
||||
export const SESSION_META_ORCHESTRATION_KEY = 'agentHost/orchestration';
|
||||
export const AH_META_ORCHESTRATION_DB_KEY = 'agentHost.orchestration';
|
||||
|
||||
export function readSessionOrchestration(meta: SessionSummaryMeta | undefined): ISessionOrchestration | undefined {
|
||||
const value = meta?.[SESSION_META_ORCHESTRATION_KEY];
|
||||
if (!value || typeof value !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
const candidate = value as { [key: string]: unknown };
|
||||
if (typeof candidate.parentSession !== 'string' || typeof candidate.coordinateWithCreator !== 'boolean') {
|
||||
return undefined;
|
||||
}
|
||||
const creatorSession = typeof candidate.creatorSession === 'string' ? candidate.creatorSession : candidate.parentSession;
|
||||
const label = typeof candidate.label === 'string' ? candidate.label : undefined;
|
||||
const notifyOnIdle = candidate.notifyOnIdle === 'once' || candidate.notifyOnIdle === 'always' ? candidate.notifyOnIdle : undefined;
|
||||
const creatorNotificationState = candidate.creatorNotificationState === 'waitingForCompletion' || candidate.creatorNotificationState === 'notified'
|
||||
? candidate.creatorNotificationState
|
||||
: undefined;
|
||||
return {
|
||||
parentSession: candidate.parentSession,
|
||||
creatorSession,
|
||||
coordinateWithCreator: candidate.coordinateWithCreator,
|
||||
...(label !== undefined ? { label } : {}),
|
||||
...(notifyOnIdle !== undefined ? { notifyOnIdle } : {}),
|
||||
...(creatorNotificationState !== undefined ? { creatorNotificationState } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseSessionOrchestration(value: string | undefined): ISessionOrchestration | undefined {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
return readSessionOrchestration({ [SESSION_META_ORCHESTRATION_KEY]: JSON.parse(value) });
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function withSessionOrchestration(meta: SessionSummaryMeta | undefined, orchestration: ISessionOrchestration): SessionSummaryMeta {
|
||||
return { ...meta, [SESSION_META_ORCHESTRATION_KEY]: orchestration };
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserved key under {@link SessionSummaryMeta} marking a session as
|
||||
* workspace-less: a session with no workspace/folder binding (surfaced in the
|
||||
|
||||
@@ -269,6 +269,8 @@ export class AgentHostStateManager extends Disposable {
|
||||
readonly onDidEmitNotification: Event<INotification> = this._onDidEmitNotification.event;
|
||||
private readonly _onDidChangeSessionActiveTurn = this._register(new Emitter<{ session: string; active: boolean }>());
|
||||
readonly onDidChangeSessionActiveTurn: Event<{ session: string; active: boolean }> = this._onDidChangeSessionActiveTurn.event;
|
||||
private readonly _onDidChangeSessionStatus = this._register(new Emitter<{ session: string; status: SessionStatus }>());
|
||||
readonly onDidChangeSessionStatus: Event<{ session: string; status: SessionStatus }> = this._onDidChangeSessionStatus.event;
|
||||
private readonly _onDidRemoveSession = this._register(new Emitter<string>());
|
||||
readonly onDidRemoveSession: Event<string> = this._onDidRemoveSession.event;
|
||||
|
||||
@@ -1706,6 +1708,9 @@ export class AgentHostStateManager extends Disposable {
|
||||
...(statusChanged ? { status: newStatus } : undefined),
|
||||
...(activityChanged ? { activity: aggregate.activity } : undefined),
|
||||
};
|
||||
if (statusChanged) {
|
||||
this._onDidChangeSessionStatus.fire({ session: sessionKey, status: newStatus });
|
||||
}
|
||||
|
||||
// Roll the aggregated `modifiedAt` into the catalog-only timestamp.
|
||||
const newModifiedAt = aggregate.modifiedAt !== undefined ? new Date(aggregate.modifiedAt).toISOString() : undefined;
|
||||
|
||||
@@ -37,7 +37,7 @@ import type { InvokeChangesetOperationParams, InvokeChangesetOperationResult } f
|
||||
import { AhpErrorCodes, AHP_SESSION_NOT_FOUND, ContentEncoding, JSON_RPC_INTERNAL_ERROR, ProtocolError, ResourceChangeType, ResourceType, ResourceWriteMode, type CreateResourceWatchParams, type CreateResourceWatchResult, type DirectoryEntry, type ResourceCopyParams, type ResourceCopyResult, type ResourceDeleteParams, type ResourceDeleteResult, type ResourceListResult, type ResourceMkdirParams, type ResourceMkdirResult, type ResourceMoveParams, type ResourceMoveResult, type ResourceReadResult, type ResourceResolveParams, type ResourceResolveResult, type ResourceWatchState, type ResourceWriteParams, type ResourceWriteResult, type IStateSnapshot } from '../common/state/sessionProtocol.js';
|
||||
import { ChangesSummary, ChatInteractivity, ChatOriginKind, MessageAttachmentKind, type Annotation, type AnnotationEntry, type AnnotationsState, type ChatOrigin, type Customization, type Message, type MessageAttachment, type MessageResourceAttachment } from '../common/state/protocol/state.js';
|
||||
import type { ChatPendingMessageSetAction, ChatTurnStartedAction, SessionConfigChangedAction } from '../common/state/protocol/actions.js';
|
||||
import { ISessionGitHubState, ISessionGitState, MessageKind, ResponsePartKind, SESSION_META_GITHUB_KEY, SESSION_META_GIT_KEY, SESSION_META_MULTI_ROOT_KEY, SESSION_META_SOURCE_CONTROL_KEY, readSessionSpawnDepth, withSessionSpawnDepth, SessionLifecycle, SessionStatus, ToolCallStatus, ToolResultContentType, AH_META_WORKSPACELESS_DB_KEY, AH_META_IS_ARCHIVED_DB_KEY, AH_META_IS_DONE_DB_KEY, AH_META_IS_READ_DB_KEY, buildChatUri, buildDefaultChatUri, buildResourceWatchChannelUri, buildSubagentChatUri, buildSubagentSessionUriPrefix, hostBuildInfoFromProduct, isAhpChatChannel, isDefaultChatUri, isSubagentChatUri, isSubagentSession, parseChatUri, parseDefaultChatUri, parseRequiredSessionUriFromChatUri, parseResourceWatchChannelUri, parseSessionMultiRootMetadata, parseSubagentSessionUri, readSessionExternal, readSessionGitHubState, readSessionGitState, readSessionMultiRootMetadata, readSessionSourceControlState, readSessionWorkspaceless, withSessionExternal, withSessionGitHubState, withSessionGitState, withSessionMultiRootMetadata, withSessionSourceControlState, withSessionStatusFlag, withSessionWorkspaceless, withSessionFolderPickerDecision, readSessionFolderPickerDecision, parseSessionFolderPickerDecision, SESSION_META_FOLDER_PICKER_KEY, readSessionEhcliAdoptable, type ISessionSourceControlState, type SessionConfigState, type SessionSummary, type ToolResultSubagentContent, type Turn, type UsageInfo, chatStorageUri, hasReportedUsage } from '../common/state/sessionState.js';
|
||||
import { ISessionGitHubState, ISessionGitState, MessageKind, ResponsePartKind, SESSION_META_GITHUB_KEY, SESSION_META_GIT_KEY, SESSION_META_MULTI_ROOT_KEY, SESSION_META_SOURCE_CONTROL_KEY, AH_META_ORCHESTRATION_DB_KEY, readSessionSpawnDepth, parseSessionOrchestration, withSessionSpawnDepth, withSessionOrchestration, SessionLifecycle, SessionStatus, ToolCallStatus, ToolResultContentType, AH_META_WORKSPACELESS_DB_KEY, AH_META_IS_ARCHIVED_DB_KEY, AH_META_IS_DONE_DB_KEY, AH_META_IS_READ_DB_KEY, buildChatUri, buildDefaultChatUri, buildResourceWatchChannelUri, buildSubagentChatUri, buildSubagentSessionUriPrefix, hostBuildInfoFromProduct, isAhpChatChannel, isDefaultChatUri, isSubagentChatUri, isSubagentSession, parseChatUri, parseDefaultChatUri, parseRequiredSessionUriFromChatUri, parseResourceWatchChannelUri, parseSessionMultiRootMetadata, parseSubagentSessionUri, readSessionExternal, readSessionGitHubState, readSessionGitState, readSessionMultiRootMetadata, readSessionSourceControlState, readSessionWorkspaceless, withSessionExternal, withSessionGitHubState, withSessionGitState, withSessionMultiRootMetadata, withSessionSourceControlState, withSessionStatusFlag, withSessionWorkspaceless, withSessionFolderPickerDecision, readSessionFolderPickerDecision, parseSessionFolderPickerDecision, SESSION_META_FOLDER_PICKER_KEY, readSessionEhcliAdoptable, type ISessionSourceControlState, type SessionConfigState, type SessionSummary, type ToolResultSubagentContent, type Turn, type UsageInfo, chatStorageUri, hasReportedUsage } from '../common/state/sessionState.js';
|
||||
import { readToolCallMeta } from '../common/meta/agentToolCallMeta.js';
|
||||
import { IProductService } from '../../product/common/productService.js';
|
||||
import { buildBoundedSideChatSourceContext, getSideChatPartialResponse } from './agentPeerChats.js';
|
||||
@@ -95,6 +95,7 @@ import { updateAgentHostTelemetryLevelFromConfig } from './agentHostTelemetrySer
|
||||
import { AgentHostActiveAgentTitleGenerationConfigKey, AgentHostEditTelemetryEnabledConfigKey, AgentHostExternalSessionsMode, AgentHostMigrateLegacyCopilotCliEnabledConfigKey, AgentHostShowExternalSessionsConfigKey, platformRootSchema } from '../common/agentHostSchema.js';
|
||||
import { AgentHostCustomizationEnablementService, IAgentHostCustomizationEnablementService } from './agentHostCustomizationEnablementService.js';
|
||||
import { AgentHostStorageService, IAgentHostStorageService } from './agentHostStorageService.js';
|
||||
import { SessionCoordinationService } from './sessionCoordination.js';
|
||||
import { AgentHostOctoKitService, IAgentHostOctoKitService } from './shared/agentHostOctoKitService.js';
|
||||
import { GitHubService, IGitHubService } from '../../github/common/githubService.js';
|
||||
import { IAgentHostChangesetService, CHANGESET_DB_METADATA_KEYS, META_CHANGES_SUMMARY } from '../common/agentHostChangesetService.js';
|
||||
@@ -332,6 +333,7 @@ export class AgentService extends Disposable implements IAgentService {
|
||||
|
||||
/** Authoritative state manager for the sessions process protocol. */
|
||||
private readonly _stateManager: AgentHostStateManager;
|
||||
private readonly _sessionCoordination: SessionCoordinationService;
|
||||
private readonly _managedSettingsService = this._register(new AgentHostManagedSettingsService());
|
||||
|
||||
/**
|
||||
@@ -589,7 +591,6 @@ export class AgentService extends Disposable implements IAgentService {
|
||||
this._queueSessionListReconciliation();
|
||||
}
|
||||
}));
|
||||
|
||||
// Build a local instantiation scope so downstream components can
|
||||
// consume {@link IAgentConfigurationService} (and later {@link ILogService})
|
||||
// via DI rather than being plumbed plain-class references.
|
||||
@@ -781,6 +782,16 @@ export class AgentService extends Disposable implements IAgentService {
|
||||
void this._gitStateService.attachSessionGitHubReferences(session.toString(), text);
|
||||
},
|
||||
}));
|
||||
this._sessionCoordination = this._register(new SessionCoordinationService(
|
||||
this._stateManager,
|
||||
this._sessionDataService,
|
||||
this._logService,
|
||||
{
|
||||
getSessionMetadata: session => this._getSessionMetadata(session),
|
||||
restoreSession: session => this.restoreSession(session),
|
||||
handleAction: (chat, action) => this._sideEffects.handleAction(chat, action),
|
||||
},
|
||||
));
|
||||
|
||||
// Server-side tools, executed in-process against each session's own
|
||||
// state. The set of groups (and their display) is the single source of
|
||||
@@ -1091,6 +1102,7 @@ export class AgentService extends Disposable implements IAgentService {
|
||||
type: ActionType.SessionMetaChanged,
|
||||
_meta: withSessionSpawnDepth(this._stateManager.getSessionSummary(session.toString())?._meta, depth),
|
||||
}),
|
||||
setSessionOrchestration: (session, orchestration) => this._sessionCoordination.setOrchestration(session.toString(), orchestration),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1688,8 +1700,8 @@ export class AgentService extends Disposable implements IAgentService {
|
||||
const sessionStr = s.session.toString();
|
||||
const changesetKeys = this._changesetCoordinator.getListMetadataKeys(sessionStr);
|
||||
const metadataKeys: Record<string, true> = changesetKeys
|
||||
? { customTitle: true, [AH_META_IS_READ_DB_KEY]: true, [AH_META_IS_ARCHIVED_DB_KEY]: true, [AH_META_IS_DONE_DB_KEY]: true, [AH_META_WORKSPACELESS_DB_KEY]: true, [SESSION_META_MULTI_ROOT_KEY]: true, [SESSION_META_FOLDER_PICKER_KEY]: true, [CHAT_BACKING_METADATA_KEY]: true, [WORKTREE_META_REPOSITORY_ROOT]: true, ...GIT_DB_METADATA_KEYS, ...changesetKeys }
|
||||
: { customTitle: true, [AH_META_IS_READ_DB_KEY]: true, [AH_META_IS_ARCHIVED_DB_KEY]: true, [AH_META_IS_DONE_DB_KEY]: true, [AH_META_WORKSPACELESS_DB_KEY]: true, [SESSION_META_MULTI_ROOT_KEY]: true, [SESSION_META_FOLDER_PICKER_KEY]: true, [CHAT_BACKING_METADATA_KEY]: true, [WORKTREE_META_REPOSITORY_ROOT]: true, ...GIT_DB_METADATA_KEYS };
|
||||
? { customTitle: true, [AH_META_IS_READ_DB_KEY]: true, [AH_META_IS_ARCHIVED_DB_KEY]: true, [AH_META_IS_DONE_DB_KEY]: true, [AH_META_ORCHESTRATION_DB_KEY]: true, [AH_META_WORKSPACELESS_DB_KEY]: true, [SESSION_META_MULTI_ROOT_KEY]: true, [SESSION_META_FOLDER_PICKER_KEY]: true, [CHAT_BACKING_METADATA_KEY]: true, [WORKTREE_META_REPOSITORY_ROOT]: true, ...GIT_DB_METADATA_KEYS, ...changesetKeys }
|
||||
: { customTitle: true, [AH_META_IS_READ_DB_KEY]: true, [AH_META_IS_ARCHIVED_DB_KEY]: true, [AH_META_IS_DONE_DB_KEY]: true, [AH_META_ORCHESTRATION_DB_KEY]: true, [AH_META_WORKSPACELESS_DB_KEY]: true, [SESSION_META_MULTI_ROOT_KEY]: true, [SESSION_META_FOLDER_PICKER_KEY]: true, [CHAT_BACKING_METADATA_KEY]: true, [WORKTREE_META_REPOSITORY_ROOT]: true, ...GIT_DB_METADATA_KEYS };
|
||||
const m = await ref.object.getMetadataObject(metadataKeys);
|
||||
// This session is an internal peer-chat backing (e.g. a
|
||||
// Claude peer chat's SDK session, enumerated by the agent's
|
||||
@@ -1711,6 +1723,10 @@ export class AgentService extends Disposable implements IAgentService {
|
||||
if (persistedArchived !== undefined) {
|
||||
updated = { ...updated, status: withSessionStatusFlag(updated.status ?? SessionStatus.Idle, SessionStatus.IsArchived, persistedArchived === 'true') };
|
||||
}
|
||||
const orchestration = parseSessionOrchestration(m[AH_META_ORCHESTRATION_DB_KEY]);
|
||||
if (orchestration) {
|
||||
updated = { ...updated, _meta: withSessionOrchestration(updated._meta, orchestration) };
|
||||
}
|
||||
if (m[META_GIT_STATE]) {
|
||||
try {
|
||||
const gitState = JSON.parse(m[META_GIT_STATE]) as ISessionGitState;
|
||||
@@ -4559,6 +4575,7 @@ export class AgentService extends Disposable implements IAgentService {
|
||||
[AH_META_IS_DONE_DB_KEY]: true,
|
||||
configValues: true,
|
||||
[AH_META_WORKSPACELESS_DB_KEY]: true,
|
||||
[AH_META_ORCHESTRATION_DB_KEY]: true,
|
||||
[SESSION_META_MULTI_ROOT_KEY]: true,
|
||||
[SESSION_META_FOLDER_PICKER_KEY]: true,
|
||||
...GIT_DB_METADATA_KEYS,
|
||||
@@ -4618,6 +4635,10 @@ export class AgentService extends Disposable implements IAgentService {
|
||||
if (m[AH_META_WORKSPACELESS_DB_KEY] !== undefined) {
|
||||
sessionMetadata = withSessionWorkspaceless(sessionMetadata, m[AH_META_WORKSPACELESS_DB_KEY] === 'true');
|
||||
}
|
||||
const orchestration = parseSessionOrchestration(m[AH_META_ORCHESTRATION_DB_KEY]);
|
||||
if (orchestration) {
|
||||
sessionMetadata = withSessionOrchestration(sessionMetadata, orchestration);
|
||||
}
|
||||
sessionMetadata = withSessionMultiRootMetadata(sessionMetadata, parseSessionMultiRootMetadata(m[SESSION_META_MULTI_ROOT_KEY]));
|
||||
sessionMetadata = withSessionFolderPickerDecision(sessionMetadata, parseSessionFolderPickerDecision(m[SESSION_META_FOLDER_PICKER_KEY]));
|
||||
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { toErrorMessage } from '../../../base/common/errorMessage.js';
|
||||
import { Disposable } from '../../../base/common/lifecycle.js';
|
||||
import { URI } from '../../../base/common/uri.js';
|
||||
import { generateUuid } from '../../../base/common/uuid.js';
|
||||
import { ILogService } from '../../log/common/log.js';
|
||||
import { ISessionDataService } from '../common/sessionDataService.js';
|
||||
import { ActionType, type ChatTurnStartedAction } from '../common/state/sessionActions.js';
|
||||
import { MessageKind, PendingMessageKind, AH_META_ORCHESTRATION_DB_KEY, buildDefaultChatUri, readSessionOrchestration, type ISessionOrchestration, SessionStatus, withSessionOrchestration } from '../common/state/sessionState.js';
|
||||
import { type Message } from '../common/state/protocol/state.js';
|
||||
import { AgentHostStateManager } from './agentHostStateManager.js';
|
||||
import { persistSessionMetadataValues } from './shared/persistSessionMetadata.js';
|
||||
|
||||
export interface ISessionCoordinationTransition {
|
||||
readonly orchestration?: ISessionOrchestration;
|
||||
readonly notify: boolean;
|
||||
}
|
||||
|
||||
export function transitionSessionCoordination(status: SessionStatus, orchestration: ISessionOrchestration): ISessionCoordinationTransition {
|
||||
if (!orchestration.notifyOnIdle) {
|
||||
return { notify: false };
|
||||
}
|
||||
|
||||
const inputNeeded = (status & SessionStatus.InputNeeded) === SessionStatus.InputNeeded;
|
||||
const inProgress = !inputNeeded && (status & SessionStatus.InProgress) === SessionStatus.InProgress
|
||||
&& (status & SessionStatus.Error) !== SessionStatus.Error;
|
||||
if (inProgress) {
|
||||
if (orchestration.creatorNotificationState !== 'waitingForCompletion'
|
||||
&& !(orchestration.notifyOnIdle === 'once' && orchestration.creatorNotificationState === 'notified')) {
|
||||
return { orchestration: { ...orchestration, creatorNotificationState: 'waitingForCompletion' }, notify: false };
|
||||
}
|
||||
return { notify: false };
|
||||
}
|
||||
|
||||
const completed = inputNeeded
|
||||
|| (status & SessionStatus.Idle) === SessionStatus.Idle
|
||||
|| (status & SessionStatus.Error) === SessionStatus.Error;
|
||||
if (!completed || orchestration.creatorNotificationState !== 'waitingForCompletion') {
|
||||
return { notify: false };
|
||||
}
|
||||
|
||||
return {
|
||||
orchestration: {
|
||||
...orchestration,
|
||||
creatorNotificationState: 'notified',
|
||||
},
|
||||
notify: true,
|
||||
};
|
||||
}
|
||||
|
||||
export interface ISessionCoordinationDelegate {
|
||||
readonly getSessionMetadata: (session: URI) => Promise<{ readonly status?: SessionStatus } | undefined>;
|
||||
readonly restoreSession: (session: URI) => Promise<void>;
|
||||
readonly handleAction: (chat: string, action: ChatTurnStartedAction) => void;
|
||||
}
|
||||
|
||||
export class SessionCoordinationService extends Disposable {
|
||||
|
||||
private readonly _queues = new Map<string, Promise<void>>();
|
||||
|
||||
constructor(
|
||||
private readonly _stateManager: AgentHostStateManager,
|
||||
private readonly _sessionDataService: ISessionDataService,
|
||||
private readonly _logService: ILogService,
|
||||
private readonly _delegate: ISessionCoordinationDelegate,
|
||||
) {
|
||||
super();
|
||||
this._register(this._stateManager.onDidChangeSessionStatus(({ session, status }) => this._queueStatusChange(session, status)));
|
||||
}
|
||||
|
||||
async setOrchestration(session: string, orchestration: ISessionOrchestration): Promise<void> {
|
||||
await persistSessionMetadataValues(this._sessionDataService, session, {
|
||||
[AH_META_ORCHESTRATION_DB_KEY]: JSON.stringify(orchestration),
|
||||
});
|
||||
this._stateManager.setSessionMeta(session, withSessionOrchestration(this._stateManager.getSessionSummary(session)?._meta, orchestration));
|
||||
}
|
||||
|
||||
async handleStatusChange(session: string, status: SessionStatus): Promise<void> {
|
||||
const summary = this._stateManager.getSessionSummary(session);
|
||||
const orchestration = readSessionOrchestration(summary?._meta);
|
||||
if (!summary || !orchestration?.notifyOnIdle) {
|
||||
return;
|
||||
}
|
||||
|
||||
const transition = transitionSessionCoordination(status, orchestration);
|
||||
if (!transition.notify) {
|
||||
if (transition.orchestration) {
|
||||
await this.setOrchestration(session, transition.orchestration);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const creator = URI.parse(orchestration.creatorSession);
|
||||
const creatorMetadata = await this._delegate.getSessionMetadata(creator);
|
||||
if (!creatorMetadata || (creatorMetadata.status !== undefined && (creatorMetadata.status & SessionStatus.IsArchived) === SessionStatus.IsArchived)) {
|
||||
return;
|
||||
}
|
||||
if (!this._stateManager.getSessionState(creator.toString())) {
|
||||
try {
|
||||
await this._delegate.restoreSession(creator);
|
||||
} catch (error) {
|
||||
this._logService.error(`[SessionCoordinationService] Failed to restore creator session ${creator.toString()} for child notification: ${toErrorMessage(error)}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const creatorSummary = this._stateManager.getSessionSummary(creator.toString());
|
||||
if (!creatorSummary || (creatorSummary.status & SessionStatus.IsArchived) === SessionStatus.IsArchived) {
|
||||
return;
|
||||
}
|
||||
|
||||
const outcome = (status & SessionStatus.InputNeeded) === SessionStatus.InputNeeded
|
||||
? 'needs input'
|
||||
: (status & SessionStatus.Error) === SessionStatus.Error ? 'encountered an error' : 'became idle';
|
||||
const childName = orchestration.label ? `${orchestration.label} (${session})` : session;
|
||||
this._startPrompt(creator, `Child session ${childName} ${outcome}. Use get_session_context with session "${session}" to inspect its result.`);
|
||||
if (transition.orchestration) {
|
||||
await this.setOrchestration(session, transition.orchestration);
|
||||
}
|
||||
}
|
||||
|
||||
private _queueStatusChange(session: string, status: SessionStatus): void {
|
||||
const previous = this._queues.get(session) ?? Promise.resolve();
|
||||
const next = previous.catch(() => undefined).then(() => this.handleStatusChange(session, status));
|
||||
this._queues.set(session, next);
|
||||
void next.catch(error => {
|
||||
this._logService.error(`[SessionCoordinationService] Failed to coordinate child session ${session}: ${toErrorMessage(error)}`);
|
||||
}).finally(() => {
|
||||
if (this._queues.get(session) === next) {
|
||||
this._queues.delete(session);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _startPrompt(creator: URI, prompt: string): void {
|
||||
const chat = buildDefaultChatUri(creator);
|
||||
const message: Message = { text: prompt, origin: { kind: MessageKind.SystemNotification } };
|
||||
if (this._stateManager.getActiveTurnId(chat)) {
|
||||
this._stateManager.dispatchServerAction(chat, {
|
||||
type: ActionType.ChatPendingMessageSet,
|
||||
kind: PendingMessageKind.Queued,
|
||||
id: generateUuid(),
|
||||
message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const action: ChatTurnStartedAction = {
|
||||
type: ActionType.ChatTurnStarted,
|
||||
turnId: generateUuid(),
|
||||
startedAt: new Date().toISOString(),
|
||||
message,
|
||||
};
|
||||
this._stateManager.dispatchServerAction(chat, action);
|
||||
this._delegate.handleAction(chat, action);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { isEqual } from '../../../../base/common/resources.js';
|
||||
import { localize } from '../../../../nls.js';
|
||||
import { AgentSession, type AgentProvider, type IAgentCreateSessionConfig, type IAgentModelInfo, type IAgentSessionMetadata } from '../../common/agent.js';
|
||||
import { SessionStatus } from '../../common/state/protocol/channels-session/state.js';
|
||||
import { buildChatUri, buildDefaultChatUri, getInlineToolInput, getSessionRelatedPullRequestUrls, isDefaultChatUri, isSessionStatusArchived, isSessionStatusRead, parseChatUri, readSessionGitState, readSessionGitHubState, ResponsePartKind, ToolCallStatus, TurnState, type Message, type ModelSelection, type ResponsePart, type ToolCallState, type ToolDefinition, type Turn, type URI as ProtocolURI } from '../../common/state/sessionState.js';
|
||||
import { buildChatUri, buildDefaultChatUri, getInlineToolInput, getSessionRelatedPullRequestUrls, isDefaultChatUri, isSessionStatusArchived, isSessionStatusRead, parseChatUri, readSessionGitState, readSessionGitHubState, readSessionOrchestration, ResponsePartKind, ToolCallStatus, TurnState, type ISessionOrchestration, type Message, type ModelSelection, type ResponsePart, type SessionIdleNotification, type ToolCallState, type ToolDefinition, type Turn, type URI as ProtocolURI } from '../../common/state/sessionState.js';
|
||||
import { buildOpenSessionLinkUri, parseOpenSessionLinkChatId, parseOpenSessionLinkUri } from '../../common/openSessionLink.js';
|
||||
import { SessionServerToolName } from '../../common/serverToolNames.js';
|
||||
import { generateUuid } from '../../../../base/common/uuid.js';
|
||||
@@ -57,15 +57,20 @@ const listSessionsInputSchema: ToolDefinition['inputSchema'] = {
|
||||
includeArchived: { type: 'boolean', description: 'Whether to include archived sessions. Defaults to false; set true to also return archived sessions.' },
|
||||
createdAfter: { type: 'string', description: 'Only return sessions created at or after this time (ISO-8601 timestamp, e.g. `2025-01-31T00:00:00Z`).' },
|
||||
createdBefore: { type: 'string', description: 'Only return sessions created at or before this time (ISO-8601 timestamp).' },
|
||||
parentSession: { type: 'string', description: 'Only return sessions created by this parent session URI or open-session link.' },
|
||||
label: { type: 'string', description: 'Only return sessions with this orchestration label.' },
|
||||
},
|
||||
};
|
||||
|
||||
const createSessionInputSchema: ToolDefinition['inputSchema'] = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
workspace: { type: 'string', description: 'Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session.' },
|
||||
workspace: { type: 'string', description: 'Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session\'s workspace and changes.' },
|
||||
prompt: { type: 'string', description: 'Initial prompt to send to the new session.' },
|
||||
model: { type: 'string', description: 'Optional model ID or display name. Defaults to the current chat\'s model.' },
|
||||
coordinateWithCreator: { type: 'boolean', description: 'Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true.' },
|
||||
notifyOnIdle: { type: 'string', enum: ['once', 'always'], description: 'Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle.' },
|
||||
label: { type: 'string', description: 'Optional label used to group and filter related child sessions.' },
|
||||
},
|
||||
required: ['workspace', 'prompt'],
|
||||
};
|
||||
@@ -149,14 +154,14 @@ export const sessionServerToolDefinitions: ToolDefinition[] = [
|
||||
{
|
||||
name: SessionServerToolName.CreateSession,
|
||||
title: 'Create Session',
|
||||
description: 'Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.',
|
||||
description: 'Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.',
|
||||
inputSchema: createSessionInputSchema,
|
||||
annotations: { readOnlyHint: false },
|
||||
},
|
||||
{
|
||||
name: SessionServerToolName.CreateChat,
|
||||
title: 'Create Chat',
|
||||
description: 'Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat\'s model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.',
|
||||
description: 'Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session\'s workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat\'s model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.',
|
||||
inputSchema: createChatInputSchema,
|
||||
annotations: { readOnlyHint: false },
|
||||
},
|
||||
@@ -200,12 +205,18 @@ interface ICreateSessionArgs {
|
||||
readonly workspace?: unknown;
|
||||
readonly prompt?: unknown;
|
||||
readonly model?: unknown;
|
||||
readonly coordinateWithCreator?: unknown;
|
||||
readonly notifyOnIdle?: unknown;
|
||||
readonly label?: unknown;
|
||||
}
|
||||
|
||||
export interface IResolvedCreateSessionArgs {
|
||||
readonly workspace: URI;
|
||||
readonly prompt: string;
|
||||
readonly model?: IAgentModelInfo;
|
||||
readonly coordinateWithCreator: boolean;
|
||||
readonly notifyOnIdle?: SessionIdleNotification;
|
||||
readonly label?: string;
|
||||
}
|
||||
|
||||
/** Minimal dependency surface needed by the session server-tool group. */
|
||||
@@ -227,6 +238,7 @@ export interface ISessionServerToolAccessor {
|
||||
readonly getSessionSpawnDepth: (session: URI) => number;
|
||||
/** Records the spawn depth of a freshly-created session so its own `create_session` calls can enforce the recursion limit. */
|
||||
readonly setSessionSpawnDepth: (session: URI, depth: number) => void;
|
||||
readonly setSessionOrchestration: (session: URI, orchestration: ISessionOrchestration) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface IRenameTitleResult {
|
||||
@@ -293,6 +305,10 @@ interface ISerializedSession {
|
||||
}[];
|
||||
readonly git?: ISerializedGitState;
|
||||
readonly github?: ISerializedGitHubState;
|
||||
readonly parentSession?: string;
|
||||
readonly creator?: string;
|
||||
readonly label?: string;
|
||||
readonly notifyOnIdle?: SessionIdleNotification;
|
||||
}
|
||||
|
||||
function getRequiredString(value: unknown, field: string, toolName: string): string {
|
||||
@@ -415,10 +431,22 @@ export function getCreateSessionArgs(rawArgs: unknown, sessions: readonly IAgent
|
||||
const workspace = getRequiredString(args.workspace, 'workspace', SessionServerToolName.CreateSession);
|
||||
const prompt = getRequiredString(args.prompt, 'prompt', SessionServerToolName.CreateSession);
|
||||
const modelName = getOptionalString(args.model, 'model', SessionServerToolName.CreateSession);
|
||||
const coordinateWithCreator = getOptionalBoolean(args.coordinateWithCreator, 'coordinateWithCreator', SessionServerToolName.CreateSession) ?? true;
|
||||
const label = getOptionalString(args.label, 'label', SessionServerToolName.CreateSession);
|
||||
let notifyOnIdle: SessionIdleNotification | undefined;
|
||||
if (args.notifyOnIdle !== undefined) {
|
||||
if (args.notifyOnIdle !== 'once' && args.notifyOnIdle !== 'always') {
|
||||
throw new Error(`Invalid ${SessionServerToolName.CreateSession} input: notifyOnIdle must be once or always.`);
|
||||
}
|
||||
notifyOnIdle = args.notifyOnIdle;
|
||||
}
|
||||
return {
|
||||
workspace: resolveWorkspace(workspace, sessions),
|
||||
prompt,
|
||||
model: resolveModel(modelName, models),
|
||||
coordinateWithCreator,
|
||||
...(notifyOnIdle !== undefined ? { notifyOnIdle } : {}),
|
||||
...(label !== undefined ? { label } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -475,6 +503,8 @@ export interface IListSessionsArgs {
|
||||
readonly createdAfter?: number;
|
||||
/** Upper bound on session creation time, in epoch milliseconds. */
|
||||
readonly createdBefore?: number;
|
||||
readonly parentSession?: string;
|
||||
readonly label?: string;
|
||||
}
|
||||
|
||||
function getOptionalBoolean(value: unknown, field: string, toolName: string): boolean | undefined {
|
||||
@@ -503,7 +533,7 @@ function getOptionalTimestamp(value: unknown, field: string, toolName: string):
|
||||
|
||||
/** Validates and normalizes the optional `list_sessions` filter arguments. */
|
||||
export function getListSessionsArgs(rawArgs: unknown): IListSessionsArgs {
|
||||
const args = (rawArgs ?? {}) as { session?: unknown; status?: unknown; workspace?: unknown; withChanges?: unknown; unread?: unknown; withPullRequest?: unknown; includeArchived?: unknown; createdAfter?: unknown; createdBefore?: unknown };
|
||||
const args = (rawArgs ?? {}) as { session?: unknown; status?: unknown; workspace?: unknown; withChanges?: unknown; unread?: unknown; withPullRequest?: unknown; includeArchived?: unknown; createdAfter?: unknown; createdBefore?: unknown; parentSession?: unknown; label?: unknown };
|
||||
|
||||
let status: Set<string> | undefined;
|
||||
if (args.status !== undefined) {
|
||||
@@ -527,6 +557,8 @@ export function getListSessionsArgs(rawArgs: unknown): IListSessionsArgs {
|
||||
includeArchived: getOptionalBoolean(args.includeArchived, 'includeArchived', SessionServerToolName.ListSessions),
|
||||
createdAfter: getOptionalTimestamp(args.createdAfter, 'createdAfter', SessionServerToolName.ListSessions),
|
||||
createdBefore: getOptionalTimestamp(args.createdBefore, 'createdBefore', SessionServerToolName.ListSessions),
|
||||
parentSession: getOptionalString(args.parentSession, 'parentSession', SessionServerToolName.ListSessions),
|
||||
label: getOptionalString(args.label, 'label', SessionServerToolName.ListSessions),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -560,14 +592,33 @@ function sessionMatchesWorkspace(session: IAgentSessionMetadata, workspace: stri
|
||||
}
|
||||
|
||||
/** Applies the {@link IListSessionsArgs} filters to a set of sessions. */
|
||||
export function filterSessions(sessions: readonly IAgentSessionMetadata[], args: IListSessionsArgs): readonly IAgentSessionMetadata[] {
|
||||
export function filterSessions(sessions: readonly IAgentSessionMetadata[], args: IListSessionsArgs, viewerSession?: string): readonly IAgentSessionMetadata[] {
|
||||
// A direct `session` lookup returns just that session, bypassing the other
|
||||
// filters (including the default archived exclusion).
|
||||
if (args.session !== undefined) {
|
||||
const target = parseOpenSessionLinkUri(args.session)?.toString() ?? args.session;
|
||||
return sessions.filter(session => session.session.toString() === target);
|
||||
}
|
||||
const requestedParent = args.parentSession !== undefined
|
||||
? parseOpenSessionLinkUri(args.parentSession)?.toString() ?? args.parentSession
|
||||
: undefined;
|
||||
const viewerCanSeeRequestedParent = requestedParent === undefined || viewerSession === undefined || viewerSession === requestedParent
|
||||
|| sessions.some(session => {
|
||||
const orchestration = readSessionOrchestration(session._meta);
|
||||
return session.session.toString() === viewerSession
|
||||
&& orchestration?.parentSession === requestedParent
|
||||
&& orchestration.coordinateWithCreator;
|
||||
});
|
||||
return sessions.filter(session => {
|
||||
const orchestration = readSessionOrchestration(session._meta);
|
||||
if (requestedParent !== undefined) {
|
||||
if (!viewerCanSeeRequestedParent || orchestration?.parentSession !== requestedParent) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (args.label !== undefined && orchestration?.label !== args.label) {
|
||||
return false;
|
||||
}
|
||||
if (args.status) {
|
||||
const names = describeSessionStatusNames(session);
|
||||
if (!names.some(name => args.status!.has(name))) {
|
||||
@@ -629,10 +680,17 @@ function serializeGitHubState(session: IAgentSessionMetadata): ISerializedGitHub
|
||||
return Object.keys(result).length > 0 ? result : undefined;
|
||||
}
|
||||
|
||||
function serializeSession(session: IAgentSessionMetadata): ISerializedSession {
|
||||
function serializeSession(session: IAgentSessionMetadata, viewerSession?: string): ISerializedSession {
|
||||
const git = serializeGitState(session);
|
||||
const github = serializeGitHubState(session);
|
||||
const status = describeSessionStatus(session);
|
||||
const orchestration = readSessionOrchestration(session._meta);
|
||||
const canSeeParent = orchestration !== undefined && (viewerSession === undefined
|
||||
|| viewerSession === orchestration.parentSession
|
||||
|| (viewerSession === session.session.toString() && orchestration.coordinateWithCreator));
|
||||
const canSeeCreator = orchestration !== undefined && orchestration.coordinateWithCreator && (viewerSession === undefined
|
||||
|| viewerSession === orchestration.creatorSession
|
||||
|| viewerSession === session.session.toString());
|
||||
return {
|
||||
session: session.session.toString(),
|
||||
...(session.summary !== undefined ? { title: session.summary } : {}),
|
||||
@@ -658,12 +716,18 @@ function serializeSession(session: IAgentSessionMetadata): ISerializedSession {
|
||||
} : {}),
|
||||
...(git !== undefined ? { git } : {}),
|
||||
...(github !== undefined ? { github } : {}),
|
||||
...(orchestration !== undefined ? {
|
||||
...(canSeeParent ? { parentSession: orchestration.parentSession } : {}),
|
||||
...(canSeeCreator ? { creator: orchestration.creatorSession } : {}),
|
||||
...(orchestration.label !== undefined ? { label: orchestration.label } : {}),
|
||||
...(orchestration.notifyOnIdle !== undefined ? { notifyOnIdle: orchestration.notifyOnIdle } : {}),
|
||||
} : {}),
|
||||
};
|
||||
}
|
||||
|
||||
/** Serializes session metadata into the compact tool-result JSON payload. */
|
||||
export function serializeSessions(sessions: readonly IAgentSessionMetadata[]): string {
|
||||
return JSON.stringify({ sessions: sessions.map(serializeSession) });
|
||||
export function serializeSessions(sessions: readonly IAgentSessionMetadata[], viewerSession?: string): string {
|
||||
return JSON.stringify({ sessions: sessions.map(session => serializeSession(session, viewerSession)) });
|
||||
}
|
||||
|
||||
export interface ICreateSessionResult {
|
||||
@@ -698,6 +762,15 @@ export async function applyCreateSessionTool(accessor: ISessionServerToolAccesso
|
||||
};
|
||||
const session = await accessor.createSession(config);
|
||||
accessor.setSessionSpawnDepth(session, parentDepth + 1);
|
||||
if (currentSession) {
|
||||
await accessor.setSessionOrchestration(session, {
|
||||
parentSession: currentSession.toString(),
|
||||
creatorSession: currentSession.toString(),
|
||||
coordinateWithCreator: args.coordinateWithCreator,
|
||||
...(args.notifyOnIdle !== undefined ? { notifyOnIdle: args.notifyOnIdle } : {}),
|
||||
...(args.label !== undefined ? { label: args.label } : {}),
|
||||
});
|
||||
}
|
||||
const chat = URI.parse(buildDefaultChatUri(session));
|
||||
await accessor.startPrompt(session, chat, args.prompt);
|
||||
return { session: session.toString(), chat: chat.toString(), openLink: buildOpenSessionLinkUri(session) };
|
||||
@@ -769,11 +842,22 @@ export function getCreateChatArgs(rawArgs: unknown, sessions: readonly IAgentSes
|
||||
return { session, prompt, ...(title !== undefined ? { title } : {}), ...(model !== undefined ? { model } : {}) };
|
||||
}
|
||||
|
||||
function assertCanCoordinateWithTarget(sessions: readonly IAgentSessionMetadata[], source: URI, target: URI, toolName: SessionServerToolName): void {
|
||||
const sourceMetadata = sessions.find(candidate => candidate.session.toString() === source.toString());
|
||||
const orchestration = readSessionOrchestration(sourceMetadata?._meta);
|
||||
if (orchestration && !orchestration.coordinateWithCreator && orchestration.creatorSession === target.toString()) {
|
||||
throw new Error(`Invalid ${toolName} input: this session is not allowed to coordinate with its creator.`);
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds a chat to a session, sends its initial prompt, and returns the created channels. */
|
||||
export async function applyCreateChatTool(accessor: ISessionServerToolAccessor, rawArgs: unknown, source?: URI): Promise<ICreateChatResult> {
|
||||
const sessions = await accessor.listSessions();
|
||||
const currentSession = source ? currentSessionUri(source.toString()) : undefined;
|
||||
const args = getCreateChatArgs(rawArgs, sessions, accessor.getModels(), currentSession);
|
||||
if (currentSession) {
|
||||
assertCanCoordinateWithTarget(sessions, currentSession, args.session, SessionServerToolName.CreateChat);
|
||||
}
|
||||
const defaults = source ? accessor.getCreationDefaults(source) : undefined;
|
||||
const targetProvider = AgentSession.provider(args.session);
|
||||
const model = args.model !== undefined ? { id: args.model.id } : targetProvider === defaults?.provider ? defaults?.model : undefined;
|
||||
@@ -952,6 +1036,10 @@ export function getSendMessageArgs(rawArgs: unknown, sessions: readonly IAgentSe
|
||||
export async function applySendMessageTool(accessor: ISessionServerToolAccessor, rawArgs: unknown, currentChannel?: ProtocolURI): Promise<string> {
|
||||
const sessions = await accessor.listSessions();
|
||||
const { session, chat, chatId, message } = getSendMessageArgs(rawArgs, sessions);
|
||||
if (currentChannel) {
|
||||
const source = currentSessionUri(currentChannel);
|
||||
assertCanCoordinateWithTarget(sessions, source, session, SessionServerToolName.SendMessage);
|
||||
}
|
||||
if (currentChannel && chat.toString() === URI.parse(currentChannel).toString()) {
|
||||
throw new Error(`Invalid ${SessionServerToolName.SendMessage} input: refusing to send a message to the current chat.`);
|
||||
}
|
||||
@@ -1151,7 +1239,7 @@ export function serializeCurrentSession(currentSession: URI, sessions: readonly
|
||||
return JSON.stringify({
|
||||
session: currentSession.toString(),
|
||||
openLink: buildOpenSessionLinkUri(currentSession),
|
||||
...(meta ? serializeSession(meta) : {}),
|
||||
...(meta ? serializeSession(meta, currentSession.toString()) : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1266,7 +1354,10 @@ export function createSessionServerToolGroup(accessor?: ISessionServerToolAccess
|
||||
const currentChannel = context.chatUri;
|
||||
switch (toolName) {
|
||||
case SessionServerToolName.ListSessions:
|
||||
return serializeSessions(filterSessions(await accessor.listSessions(), getListSessionsArgs(rawArgs)));
|
||||
{
|
||||
const viewerSession = currentSessionUri(currentChannel).toString();
|
||||
return serializeSessions(filterSessions(await accessor.listSessions(), getListSessionsArgs(rawArgs), viewerSession), viewerSession);
|
||||
}
|
||||
case SessionServerToolName.GetCurrentSession:
|
||||
return serializeCurrentSession(currentSessionUri(currentChannel), await accessor.listSessions());
|
||||
case SessionServerToolName.CreateSession: {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/c
|
||||
import { runWithFakedTimers } from '../../../../base/test/common/timeTravelScheduler.js';
|
||||
import { NullLogService } from '../../../log/common/log.js';
|
||||
import { ActionType, NotificationType, type ActionEnvelope, type INotification } from '../../common/state/sessionActions.js';
|
||||
import { MessageKind, SessionSummary, ResponsePartKind, ROOT_STATE_URI, SessionLifecycle, SessionStatus, TurnState, buildChatUri, buildDefaultChatUri, buildSubagentSessionUri, buildSubagentSessionUriPrefix, isSubagentSession, mergeSessionWithDefaultChat, parseSubagentSessionUri, readHostBuildInfo, readSessionEhcliAdoptable, withSessionEhcliAdoptable, type ChatState, type MarkdownResponsePart, type SessionState, type Turn } from '../../common/state/sessionState.js';
|
||||
import { ChatInputQuestionKind, ChatInputRequestPurpose, ChatInputResponseKind, MessageKind, SessionSummary, ResponsePartKind, ROOT_STATE_URI, SessionLifecycle, SessionStatus, TurnState, buildChatUri, buildDefaultChatUri, buildSubagentSessionUri, buildSubagentSessionUriPrefix, isSubagentSession, mergeSessionWithDefaultChat, parseSubagentSessionUri, readHostBuildInfo, readSessionEhcliAdoptable, withSessionEhcliAdoptable, type ChatState, type MarkdownResponsePart, type SessionState, type Turn } from '../../common/state/sessionState.js';
|
||||
import { type SessionSummaryChangedParams } from '../../common/state/protocol/notifications.js';
|
||||
import { AgentHostStateManager } from '../../node/agentHostStateManager.js';
|
||||
import { buildChangesetUri, buildSessionChangesetUri } from '../../common/changesetUri.js';
|
||||
@@ -1430,6 +1430,7 @@ suite('AgentHostStateManager', () => {
|
||||
startedAt: '2025-01-01T00:00:00.000Z',
|
||||
message: { text: 'a', origin: { kind: MessageKind.User } },
|
||||
});
|
||||
|
||||
manager.dispatchServerAction(peerChat, {
|
||||
type: ActionType.ChatTurnStarted,
|
||||
turnId: 'turn-peer',
|
||||
@@ -1468,6 +1469,45 @@ suite('AgentHostStateManager', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('session-status event captures every lifecycle transition without debouncing', () => {
|
||||
manager.createSession(makeSessionSummary());
|
||||
const defaultChat = buildDefaultChatUri(sessionUri);
|
||||
const statuses: SessionStatus[] = [];
|
||||
disposables.add(manager.onDidChangeSessionStatus(e => statuses.push(e.status & ~(SessionStatus.IsRead | SessionStatus.IsArchived))));
|
||||
|
||||
manager.dispatchServerAction(defaultChat, {
|
||||
type: ActionType.ChatTurnStarted,
|
||||
turnId: 'turn-default',
|
||||
startedAt: '2025-01-01T00:00:00.000Z',
|
||||
message: { text: 'a', origin: { kind: MessageKind.User } },
|
||||
});
|
||||
manager.dispatchServerAction(defaultChat, {
|
||||
type: ActionType.ChatInputRequested,
|
||||
request: {
|
||||
id: 'request',
|
||||
purpose: ChatInputRequestPurpose.AskUser,
|
||||
questions: [{ kind: ChatInputQuestionKind.Text, id: 'question', message: 'Continue?' }],
|
||||
},
|
||||
});
|
||||
manager.dispatchServerAction(defaultChat, {
|
||||
type: ActionType.ChatInputCompleted,
|
||||
requestId: 'request',
|
||||
response: ChatInputResponseKind.Accept,
|
||||
});
|
||||
manager.dispatchServerAction(defaultChat, {
|
||||
type: ActionType.ChatTurnComplete,
|
||||
turnId: 'turn-default',
|
||||
duration: 1000,
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(statuses, [
|
||||
SessionStatus.InProgress,
|
||||
SessionStatus.InputNeeded,
|
||||
SessionStatus.InProgress,
|
||||
SessionStatus.Idle,
|
||||
]);
|
||||
});
|
||||
|
||||
test('removeChat clears a peer chat that is removed mid-turn', () => {
|
||||
manager.createSession(makeSessionSummary());
|
||||
const defaultChat = buildDefaultChatUri(sessionUri);
|
||||
|
||||
@@ -39,7 +39,7 @@ import { META_GITHUB_STATE, META_SOURCE_CONTROL_STATE } from '../../common/agent
|
||||
import { SessionConfigKey } from '../../common/sessionConfigKeys.js';
|
||||
import { SessionDatabase } from '../../node/sessionDatabase.js';
|
||||
import { ActionType, ActionEnvelope, NotificationType } from '../../common/state/sessionActions.js';
|
||||
import { AH_META_IS_READ_DB_KEY, AH_META_WORKSPACELESS_DB_KEY, ChangesetStatus, CustomizationType, MessageAttachmentKind, MessageKind, SessionActiveClient, ResponsePartKind, ROOT_STATE_URI, SESSION_META_FOLDER_PICKER_KEY, SESSION_META_MULTI_ROOT_KEY, SessionLifecycle, SessionSourceControlOutcome, SessionStatus, ToolCallCancellationReason, ToolCallConfirmationReason, ToolCallStatus, ToolResultContentType, TurnState, buildChatUri, buildDefaultChatUri, buildSubagentChatUri, buildSubagentSessionUri, customizationId, isDefaultChatUri, isSubagentSession, parseChatUri, parseSubagentSessionUri, readSessionEhcliAdoptable, readSessionExternal, readSessionGitHubState, readSessionMultiRootMetadata, readSessionFolderPickerDecision, readSessionSourceControlState, withSessionEhcliAdoptable, withSessionMultiRootMetadata, ChatOriginKind, type ChangesetState, type ISessionFolderPickerDecision, type ISessionWithDefaultChat, type MarkdownResponsePart, type SessionState, type ToolCallCompletedState, type ToolCallResponsePart, type Turn } from '../../common/state/sessionState.js';
|
||||
import { AH_META_IS_READ_DB_KEY, AH_META_ORCHESTRATION_DB_KEY, AH_META_WORKSPACELESS_DB_KEY, ChangesetStatus, CustomizationType, MessageAttachmentKind, MessageKind, SessionActiveClient, ResponsePartKind, ROOT_STATE_URI, SESSION_META_FOLDER_PICKER_KEY, SESSION_META_MULTI_ROOT_KEY, SessionLifecycle, SessionSourceControlOutcome, SessionStatus, ToolCallCancellationReason, ToolCallConfirmationReason, ToolCallStatus, ToolResultContentType, TurnState, buildChatUri, buildDefaultChatUri, buildSubagentChatUri, buildSubagentSessionUri, customizationId, isDefaultChatUri, isSubagentSession, parseChatUri, parseSubagentSessionUri, readSessionEhcliAdoptable, readSessionExternal, readSessionGitHubState, readSessionMultiRootMetadata, readSessionFolderPickerDecision, readSessionOrchestration, readSessionSourceControlState, withSessionEhcliAdoptable, withSessionMultiRootMetadata, ChatOriginKind, type ChangesetState, type ISessionFolderPickerDecision, type ISessionOrchestration, type ISessionWithDefaultChat, type MarkdownResponsePart, type SessionState, type ToolCallCompletedState, type ToolCallResponsePart, type Turn } from '../../common/state/sessionState.js';
|
||||
import { ChatInteractivity, type MessageAttachment } from '../../common/state/protocol/state.js';
|
||||
import { IProductService } from '../../../product/common/productService.js';
|
||||
import { AgentService } from '../../node/agentService.js';
|
||||
@@ -6124,6 +6124,90 @@ suite('AgentService (node dispatcher)', () => {
|
||||
assert.deepStrictEqual(readSessionMultiRootMetadata(localService.stateManager.getSessionState(sessionResource.toString())?._meta), multiRoot);
|
||||
});
|
||||
|
||||
test('restores persisted orchestration metadata', async () => {
|
||||
const db = new TestSessionDatabase();
|
||||
const localService = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(db), { _serviceBrand: undefined } as IProductService, createNoopGitService()));
|
||||
localService.registerProvider(copilotAgent);
|
||||
await createAgentSession(copilotAgent);
|
||||
const sessionResource = (await copilotAgent.listSessions())[0].session;
|
||||
copilotAgent.sessionMessages = [];
|
||||
const orchestration = {
|
||||
parentSession: 'copilot:/parent',
|
||||
creatorSession: 'copilot:/creator',
|
||||
coordinateWithCreator: true,
|
||||
notifyOnIdle: 'always',
|
||||
} as const;
|
||||
await db.setMetadata(AH_META_ORCHESTRATION_DB_KEY, JSON.stringify(orchestration));
|
||||
|
||||
await localService.restoreSession(sessionResource);
|
||||
|
||||
assert.deepStrictEqual(readSessionOrchestration(localService.stateManager.getSessionState(sessionResource.toString())?._meta), orchestration);
|
||||
});
|
||||
|
||||
test('does not consume a child notification when its creator cannot be resolved', async () => {
|
||||
const sessionData = createPerSessionDataService();
|
||||
const localService = disposables.add(new AgentService(new NullLogService(), fileService, sessionData.service, { _serviceBrand: undefined } as IProductService, createNoopGitService()));
|
||||
localService.registerProvider(copilotAgent);
|
||||
const child = await localService.createSession({ provider: 'copilot' });
|
||||
const orchestration: ISessionOrchestration = {
|
||||
parentSession: 'copilot:/missing',
|
||||
creatorSession: 'copilot:/missing',
|
||||
coordinateWithCreator: true,
|
||||
notifyOnIdle: 'once',
|
||||
creatorNotificationState: 'waitingForCompletion',
|
||||
};
|
||||
const coordinator = localService as unknown as {
|
||||
_sessionCoordination: {
|
||||
setOrchestration(session: string, value: ISessionOrchestration): Promise<void>;
|
||||
handleStatusChange(session: string, status: SessionStatus): Promise<void>;
|
||||
};
|
||||
};
|
||||
await coordinator._sessionCoordination.setOrchestration(child.toString(), orchestration);
|
||||
|
||||
await coordinator._sessionCoordination.handleStatusChange(child.toString(), SessionStatus.Idle);
|
||||
|
||||
assert.deepStrictEqual(readSessionOrchestration(localService.stateManager.getSessionSummary(child.toString())?._meta), orchestration);
|
||||
});
|
||||
|
||||
test('restores a cold creator before delivering and consuming a child notification', async () => {
|
||||
const sessionData = createPerSessionDataService();
|
||||
const localService = disposables.add(new AgentService(new NullLogService(), fileService, sessionData.service, { _serviceBrand: undefined } as IProductService, createNoopGitService()));
|
||||
localService.registerProvider(copilotAgent);
|
||||
const creator = await localService.createSession({ provider: 'copilot' });
|
||||
const child = await localService.createSession({ provider: 'copilot' });
|
||||
const orchestration: ISessionOrchestration = {
|
||||
parentSession: creator.toString(),
|
||||
creatorSession: creator.toString(),
|
||||
coordinateWithCreator: true,
|
||||
notifyOnIdle: 'once',
|
||||
creatorNotificationState: 'waitingForCompletion',
|
||||
};
|
||||
const coordinator = localService as unknown as {
|
||||
_sessionCoordination: {
|
||||
setOrchestration(session: string, value: ISessionOrchestration): Promise<void>;
|
||||
handleStatusChange(session: string, status: SessionStatus): Promise<void>;
|
||||
};
|
||||
};
|
||||
await coordinator._sessionCoordination.setOrchestration(child.toString(), orchestration);
|
||||
localService.stateManager.removeSession(creator.toString());
|
||||
assert.strictEqual(localService.stateManager.getSessionState(creator.toString()), undefined);
|
||||
let notificationStarted = false;
|
||||
disposables.add(localService.stateManager.onDidEmitEnvelope(envelope => {
|
||||
if (envelope.channel === buildDefaultChatUri(creator) && envelope.action.type === ActionType.ChatTurnStarted && envelope.action.message.origin.kind === MessageKind.SystemNotification) {
|
||||
notificationStarted = true;
|
||||
}
|
||||
}));
|
||||
|
||||
await coordinator._sessionCoordination.handleStatusChange(child.toString(), SessionStatus.Idle);
|
||||
|
||||
assert.ok(localService.stateManager.getSessionState(creator.toString()));
|
||||
assert.strictEqual(notificationStarted, true);
|
||||
assert.deepStrictEqual(readSessionOrchestration(localService.stateManager.getSessionSummary(child.toString())?._meta), {
|
||||
...orchestration,
|
||||
creatorNotificationState: 'notified',
|
||||
});
|
||||
});
|
||||
|
||||
test('restores persisted source-control provenance', async () => {
|
||||
const db = new TestSessionDatabase();
|
||||
const localService = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(db), { _serviceBrand: undefined } as IProductService, createNoopGitService()));
|
||||
|
||||
+27
-3
@@ -1214,6 +1214,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1229,14 +1237,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1245,6 +1253,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1255,7 +1279,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1214,6 +1214,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1229,14 +1237,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1245,6 +1253,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1255,7 +1279,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1214,6 +1214,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1229,14 +1237,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1245,6 +1253,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1255,7 +1279,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1220,6 +1220,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1235,14 +1243,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1251,6 +1259,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1261,7 +1285,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1224,6 +1224,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1239,14 +1247,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1255,6 +1263,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1265,7 +1289,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1224,6 +1224,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1239,14 +1247,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1255,6 +1263,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1265,7 +1289,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1214,6 +1214,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1229,14 +1237,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1245,6 +1253,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1255,7 +1279,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1214,6 +1214,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1229,14 +1237,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1245,6 +1253,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1255,7 +1279,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1223,6 +1223,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1238,14 +1246,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1254,6 +1262,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1264,7 +1288,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1259,6 +1259,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1274,14 +1282,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1290,6 +1298,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1300,7 +1324,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1168,6 +1168,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1183,14 +1191,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1199,6 +1207,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1209,7 +1233,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1206,6 +1206,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1221,14 +1229,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1237,6 +1245,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1247,7 +1271,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1220,6 +1220,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1235,14 +1243,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1251,6 +1259,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1261,7 +1285,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1168,6 +1168,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1183,14 +1191,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1199,6 +1207,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1209,7 +1233,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1168,6 +1168,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1183,14 +1191,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1199,6 +1207,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1209,7 +1233,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1220,6 +1220,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1235,14 +1243,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1251,6 +1259,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1261,7 +1285,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1182,6 +1182,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1197,14 +1205,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1213,6 +1221,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1223,7 +1247,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1182,6 +1182,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1197,14 +1205,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1213,6 +1221,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1223,7 +1247,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
+27
-3
@@ -1182,6 +1182,14 @@ List sessions and their compact metadata (status, activity, working directory, p
|
||||
"createdBefore": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created at or before this time (ISO-8601 timestamp)."
|
||||
},
|
||||
"parentSession": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions created by this parent session URI or open-session link."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Only return sessions with this orchestration label."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1197,14 +1205,14 @@ Get metadata and the open link for the session this conversation is running in.
|
||||
```
|
||||
|
||||
#### create_session
|
||||
Create a session in a workspace and start it with an initial prompt. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
Create an independently scoped session and start it with an initial prompt. Use this when work needs a separate workspace, worktree or branch, provider, or lifecycle. For parallel subtasks that should share one workspace and aggregate diff, prefer `create_chat`. The UI shows a "Session Created" confirmation with a button to open it, so reply with a single short sentence confirming the session was created and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspace": {
|
||||
"type": "string",
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session."
|
||||
"description": "Unique project name, project/workspace URI, absolute folder path, or working directory from an existing session. Use `create_chat` instead when the work should share the current session's workspace and changes."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
@@ -1213,6 +1221,22 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model ID or display name. Defaults to the current chat's model."
|
||||
},
|
||||
"coordinateWithCreator": {
|
||||
"type": "boolean",
|
||||
"description": "Allow the child to identify and contact the session that created it. Set false for an independent child that must not send messages or create chats in its creator. Defaults to true."
|
||||
},
|
||||
"notifyOnIdle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"once",
|
||||
"always"
|
||||
],
|
||||
"description": "Wake the creator when the child needs input, becomes idle, or errors, either once or after every work cycle."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Optional label used to group and filter related child sessions."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1223,7 +1247,7 @@ Create a session in a workspace and start it with an initial prompt. The UI show
|
||||
```
|
||||
|
||||
#### create_chat
|
||||
Add a new chat to an existing session and start it with an initial prompt. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
Add a new chat to an existing session and start it with an initial prompt. Prefer this for parallel subtasks that should remain part of one user-visible unit of work, sharing the session's workspace, lifecycle, and aggregate diff. Omit `session` to add the chat to the current session; otherwise pass a session URI from `list_sessions`. Optionally pass a `model` to use for the chat (defaults to the current chat's model). The UI shows a "Chat Created" confirmation with a button to open the session, so reply with a single short sentence and do NOT print the session URL or tell the user to click a button.
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
|
||||
@@ -18,6 +18,7 @@ import type { ListSessionsResult, SubscribeResult } from '../../../../common/sta
|
||||
import { ActionType, NotificationType, type ChatToolCallCompleteAction, type ChatToolCallStartAction, type SessionAddedParams, type StateAction } from '../../../../common/state/sessionActions.js';
|
||||
import {
|
||||
buildDefaultChatUri,
|
||||
readSessionOrchestration,
|
||||
ROOT_STATE_URI,
|
||||
type AnnotationsState,
|
||||
type ChatState,
|
||||
@@ -865,6 +866,8 @@ export function defineServerToolsTests(context: IAgentHostE2ETestContext): void
|
||||
}, 30_000);
|
||||
const child = (childAdded.params as SessionAddedParams).summary;
|
||||
createdSessions.push(child.resource);
|
||||
const orchestration = readSessionOrchestration(child._meta);
|
||||
assert.ok(orchestration, 'child SessionAdded summary should include orchestration metadata');
|
||||
const childRequest = await retry(async () => {
|
||||
const requests = context.observedModelRequestBodies
|
||||
.map(summarizeAnthropicRequest)
|
||||
@@ -881,11 +884,17 @@ export function defineServerToolsTests(context: IAgentHostE2ETestContext): void
|
||||
provider: child.provider,
|
||||
messages: childState.turns.map(turn => turn.message.text),
|
||||
childRequestModel: childRequest.model,
|
||||
orchestration,
|
||||
}, {
|
||||
sawPendingConfirmation: true,
|
||||
provider: model.provider,
|
||||
messages: [childPrompt],
|
||||
childRequestModel: model.id,
|
||||
orchestration: {
|
||||
parentSession: session.sessionUri,
|
||||
creatorSession: session.sessionUri,
|
||||
coordinateWithCreator: true,
|
||||
},
|
||||
});
|
||||
}, supportsProviderModelSessionCreation);
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import assert from 'assert';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
|
||||
import { SessionStatus, type ISessionOrchestration } from '../../common/state/sessionState.js';
|
||||
import { transitionSessionCoordination } from '../../node/sessionCoordination.js';
|
||||
|
||||
suite('SessionCoordination', () => {
|
||||
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
const base: ISessionOrchestration = {
|
||||
parentSession: 'copilot:/parent',
|
||||
creatorSession: 'copilot:/creator',
|
||||
coordinateWithCreator: true,
|
||||
notifyOnIdle: 'once',
|
||||
};
|
||||
|
||||
test('waits for completion only after work starts', () => {
|
||||
assert.deepStrictEqual(transitionSessionCoordination(SessionStatus.Idle, base), { notify: false });
|
||||
assert.deepStrictEqual(transitionSessionCoordination(SessionStatus.InProgress, base), {
|
||||
orchestration: { ...base, creatorNotificationState: 'waitingForCompletion' },
|
||||
notify: false,
|
||||
});
|
||||
});
|
||||
|
||||
test('notifies once after idle or error', () => {
|
||||
const waiting = { ...base, creatorNotificationState: 'waitingForCompletion' as const };
|
||||
const expected = {
|
||||
orchestration: { ...waiting, creatorNotificationState: 'notified' as const },
|
||||
notify: true,
|
||||
};
|
||||
assert.deepStrictEqual(transitionSessionCoordination(SessionStatus.Idle, waiting), expected);
|
||||
assert.deepStrictEqual(transitionSessionCoordination(SessionStatus.Error, waiting), expected);
|
||||
assert.deepStrictEqual(transitionSessionCoordination(SessionStatus.InProgress, expected.orchestration), { notify: false });
|
||||
});
|
||||
|
||||
test('notifies once when input is needed and deduplicates repeated status', () => {
|
||||
const waiting = { ...base, creatorNotificationState: 'waitingForCompletion' as const };
|
||||
const transition = transitionSessionCoordination(SessionStatus.InputNeeded, waiting);
|
||||
assert.deepStrictEqual(transition, {
|
||||
orchestration: { ...waiting, creatorNotificationState: 'notified' },
|
||||
notify: true,
|
||||
});
|
||||
assert.deepStrictEqual(transitionSessionCoordination(SessionStatus.InputNeeded, transition.orchestration!), { notify: false });
|
||||
});
|
||||
|
||||
test('always waits for later work to complete', () => {
|
||||
const always: ISessionOrchestration = { ...base, notifyOnIdle: 'always', creatorNotificationState: 'notified' };
|
||||
assert.deepStrictEqual(transitionSessionCoordination(SessionStatus.InProgress, always), {
|
||||
orchestration: { ...always, creatorNotificationState: 'waitingForCompletion' },
|
||||
notify: false,
|
||||
});
|
||||
});
|
||||
|
||||
test('always captures back-to-back work cycles', () => {
|
||||
let orchestration: ISessionOrchestration = { ...base, notifyOnIdle: 'always' };
|
||||
for (let cycle = 0; cycle < 2; cycle++) {
|
||||
const started = transitionSessionCoordination(SessionStatus.InProgress, orchestration);
|
||||
assert.strictEqual(started.notify, false);
|
||||
orchestration = started.orchestration!;
|
||||
const completed = transitionSessionCoordination(SessionStatus.Idle, orchestration);
|
||||
assert.strictEqual(completed.notify, true);
|
||||
orchestration = completed.orchestration!;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/c
|
||||
import { NullLogService } from '../../../log/common/log.js';
|
||||
import type { IAgentCreateSessionConfig, IAgentModelInfo, IAgentSessionMetadata } from '../../common/agent.js';
|
||||
import { SessionStatus } from '../../common/state/protocol/channels-session/state.js';
|
||||
import { buildChatUri, buildDefaultChatUri, MessageKind, ResponsePartKind, ToolCallConfirmationReason, ToolCallStatus, TurnState, withSessionGitState, withSessionGitHubState, type ModelSelection, type ResponsePart, type ToolCallState, type Turn } from '../../common/state/sessionState.js';
|
||||
import { buildChatUri, buildDefaultChatUri, MessageKind, ResponsePartKind, ToolCallConfirmationReason, ToolCallStatus, TurnState, withSessionGitState, withSessionGitHubState, withSessionOrchestration, type ISessionOrchestration, type ModelSelection, type ResponsePart, type ToolCallState, type Turn } from '../../common/state/sessionState.js';
|
||||
import { AgentHostStateManager } from '../../node/agentHostStateManager.js';
|
||||
import { SessionServerToolName } from '../../common/serverToolNames.js';
|
||||
import { AgentServerToolHost } from '../../node/shared/agentServerToolHost.js';
|
||||
@@ -54,8 +54,9 @@ suite('SessionServerTools', () => {
|
||||
return { sessionUri, chatUri: buildDefaultChatUri(sessionUri) };
|
||||
}
|
||||
|
||||
function createAccessor(overrides?: Partial<ISessionServerToolAccessor> & { onCreate?: (config: IAgentCreateSessionConfig) => void; onPrompt?: (session: URI, chat: URI, prompt: string) => void; onCreateChat?: (session: URI, chat: URI, options?: { title?: string; model?: ModelSelection }) => void; onRenameChat?: (session: URI, chat: URI, title: string) => void; onDelete?: (session: URI) => void; depths?: Map<string, number> }): ISessionServerToolAccessor {
|
||||
function createAccessor(overrides?: Partial<ISessionServerToolAccessor> & { onCreate?: (config: IAgentCreateSessionConfig) => void; onPrompt?: (session: URI, chat: URI, prompt: string) => void; onCreateChat?: (session: URI, chat: URI, options?: { title?: string; model?: ModelSelection }) => void; onRenameChat?: (session: URI, chat: URI, title: string) => void; onDelete?: (session: URI) => void; depths?: Map<string, number>; orchestrations?: Map<string, ISessionOrchestration> }): ISessionServerToolAccessor {
|
||||
const depths = overrides?.depths ?? new Map<string, number>();
|
||||
const orchestrations = overrides?.orchestrations ?? new Map<string, ISessionOrchestration>();
|
||||
return {
|
||||
isActiveAgentTitleGenerationEnabled: overrides?.isActiveAgentTitleGenerationEnabled ?? (() => true),
|
||||
listSessions: overrides?.listSessions ?? (async () => [sessionMeta('s1', SessionStatus.InProgress, workspace)]),
|
||||
@@ -71,6 +72,7 @@ suite('SessionServerTools', () => {
|
||||
getChatContext: overrides?.getChatContext ?? (async () => undefined),
|
||||
getSessionSpawnDepth: overrides?.getSessionSpawnDepth ?? (session => depths.get(session.toString()) ?? 0),
|
||||
setSessionSpawnDepth: overrides?.setSessionSpawnDepth ?? ((session, depth) => { depths.set(session.toString(), depth); }),
|
||||
setSessionOrchestration: overrides?.setSessionOrchestration ?? (async (session, orchestration) => { orchestrations.set(session.toString(), orchestration); }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,6 +86,7 @@ suite('SessionServerTools', () => {
|
||||
assert.strictEqual(sessionToolRequiresConfirmation(SessionServerToolName.ListSessions), false);
|
||||
assert.strictEqual(sessionToolRequiresConfirmation(SessionServerToolName.GetCurrentSession), false);
|
||||
assert.strictEqual(sessionToolRequiresConfirmation(SessionServerToolName.GetSessionContext), false);
|
||||
assert.strictEqual(sessionServerToolDefinitions.find(def => def.name === SessionServerToolName.CreateSession)?.inputSchema?.properties?.parentSession, undefined);
|
||||
assert.deepStrictEqual(sessionServerToolDefinitions.slice(4, 5).map(def => ({ name: def.name, required: def.inputSchema?.required })), [
|
||||
{ name: SessionServerToolName.RenameChat, required: ['title'] },
|
||||
]);
|
||||
@@ -219,6 +222,77 @@ suite('SessionServerTools', () => {
|
||||
});
|
||||
});
|
||||
|
||||
suite('orchestration metadata', () => {
|
||||
test('serializeSessions and filters expose orchestration relationships', () => {
|
||||
const child = {
|
||||
...sessionMeta('child', SessionStatus.Idle, workspace),
|
||||
_meta: withSessionOrchestration(undefined, {
|
||||
parentSession: 'copilot:/parent',
|
||||
creatorSession: 'copilot:/creator',
|
||||
coordinateWithCreator: true,
|
||||
notifyOnIdle: 'once',
|
||||
label: 'research',
|
||||
}),
|
||||
};
|
||||
|
||||
assert.deepStrictEqual({
|
||||
serialized: JSON.parse(serializeSessions([child])).sessions[0],
|
||||
byParent: filterSessions([child], getListSessionsArgs({ parentSession: 'agent-host-session://copilot/parent' })).map(session => session.session.toString()),
|
||||
byLabel: filterSessions([child], getListSessionsArgs({ label: 'research' })).map(session => session.session.toString()),
|
||||
}, {
|
||||
serialized: {
|
||||
session: 'copilot:/child',
|
||||
title: 'title-child',
|
||||
status: 'idle',
|
||||
workingDirectory: workspace.toString(),
|
||||
parentSession: 'copilot:/parent',
|
||||
creator: 'copilot:/creator',
|
||||
label: 'research',
|
||||
notifyOnIdle: 'once',
|
||||
},
|
||||
byParent: ['copilot:/child'],
|
||||
byLabel: ['copilot:/child'],
|
||||
});
|
||||
});
|
||||
|
||||
test('serializeSessions hides a disabled creator relationship from the child', () => {
|
||||
const child = {
|
||||
...sessionMeta('child', SessionStatus.Idle, workspace),
|
||||
_meta: withSessionOrchestration(undefined, {
|
||||
parentSession: 'copilot:/parent',
|
||||
creatorSession: 'copilot:/parent',
|
||||
coordinateWithCreator: false,
|
||||
label: 'private-child',
|
||||
}),
|
||||
};
|
||||
|
||||
assert.deepStrictEqual({
|
||||
child: JSON.parse(serializeSessions([child], 'copilot:/child')).sessions[0],
|
||||
parent: JSON.parse(serializeSessions([child], 'copilot:/parent')).sessions[0],
|
||||
childFilter: filterSessions([child], getListSessionsArgs({ parentSession: 'copilot:/parent' }), 'copilot:/child'),
|
||||
parentFilter: filterSessions([child], getListSessionsArgs({ parentSession: 'copilot:/parent' }), 'copilot:/parent').map(session => session.session.toString()),
|
||||
}, {
|
||||
child: {
|
||||
session: 'copilot:/child',
|
||||
title: 'title-child',
|
||||
status: 'idle',
|
||||
workingDirectory: workspace.toString(),
|
||||
label: 'private-child',
|
||||
},
|
||||
parent: {
|
||||
session: 'copilot:/child',
|
||||
title: 'title-child',
|
||||
status: 'idle',
|
||||
workingDirectory: workspace.toString(),
|
||||
parentSession: 'copilot:/parent',
|
||||
label: 'private-child',
|
||||
},
|
||||
childFilter: [],
|
||||
parentFilter: ['copilot:/child'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('serializeSessions preserves remote project roots and multiple working directories', () => {
|
||||
const project = URI.parse('vscode-remote://ssh-remote+example/home/me/app');
|
||||
const primary = URI.parse('vscode-remote://ssh-remote+example/home/me/app-worktree');
|
||||
@@ -279,6 +353,7 @@ suite('SessionServerTools', () => {
|
||||
assert.strictEqual(byId.model?.id, 'gpt-4o');
|
||||
const byName = getCreateSessionArgs({ workspace: workspace.toString(), prompt: 'hi', model: 'GPT-4o' }, sessions, [model]);
|
||||
assert.strictEqual(byName.model?.name, 'GPT-4o');
|
||||
assert.strictEqual(byName.coordinateWithCreator, true);
|
||||
});
|
||||
|
||||
test('getCreateSessionArgs resolves a unique project name to its configured root', () => {
|
||||
@@ -329,7 +404,8 @@ suite('SessionServerTools', () => {
|
||||
const stateManager = store.add(new AgentHostStateManager(new NullLogService()));
|
||||
let created: IAgentCreateSessionConfig | undefined;
|
||||
let prompted: { chat: URI; prompt: string } | undefined;
|
||||
const accessor = createAccessor({ onCreate: c => { created = c; }, onPrompt: (_s, chat, prompt) => { prompted = { chat, prompt }; } });
|
||||
const orchestrations = new Map<string, ISessionOrchestration>();
|
||||
const accessor = createAccessor({ orchestrations, onCreate: c => { created = c; }, onPrompt: (_s, chat, prompt) => { prompted = { chat, prompt }; } });
|
||||
const group = createSessionServerToolGroup(accessor);
|
||||
|
||||
const text = await group.execute(stateManager, executionContext('copilot:/caller'), SessionServerToolName.CreateSession, { workspace: workspace.toString(), prompt: 'do it', model: 'gpt-4o' });
|
||||
@@ -339,9 +415,36 @@ suite('SessionServerTools', () => {
|
||||
assert.strictEqual(prompted?.chat.toString(), buildDefaultChatUri(URI.parse('copilot:/new')));
|
||||
assert.ok(text.includes('agent-host-session://copilot/new'), 'result carries the open-session link for the pill');
|
||||
assert.ok(!text.includes('copilot:/new'), 'result does not echo the raw backend session URI');
|
||||
assert.deepStrictEqual(orchestrations.get('copilot:/new'), {
|
||||
parentSession: 'copilot:/caller',
|
||||
creatorSession: 'copilot:/caller',
|
||||
coordinateWithCreator: true,
|
||||
});
|
||||
store.dispose();
|
||||
});
|
||||
|
||||
test('create_session records explicit orchestration options', async () => {
|
||||
const orchestrations = new Map<string, ISessionOrchestration>();
|
||||
const sessions = [sessionMeta('caller', SessionStatus.InProgress, workspace)];
|
||||
const accessor = createAccessor({ orchestrations, listSessions: async () => sessions });
|
||||
|
||||
await applyCreateSessionTool(accessor, {
|
||||
workspace: workspace.toString(),
|
||||
prompt: 'do it',
|
||||
coordinateWithCreator: false,
|
||||
notifyOnIdle: 'always',
|
||||
label: 'research',
|
||||
}, URI.parse('copilot:/caller'));
|
||||
|
||||
assert.deepStrictEqual(orchestrations.get('copilot:/new'), {
|
||||
parentSession: 'copilot:/caller',
|
||||
creatorSession: 'copilot:/caller',
|
||||
coordinateWithCreator: false,
|
||||
notifyOnIdle: 'always',
|
||||
label: 'research',
|
||||
});
|
||||
});
|
||||
|
||||
test('create_session inherits the calling chat model and permission config', async () => {
|
||||
const source = URI.parse(buildChatUri('copilot:/caller', 'peer'));
|
||||
let creationSource: URI | undefined;
|
||||
@@ -506,7 +609,7 @@ suite('SessionServerTools', () => {
|
||||
});
|
||||
|
||||
test('getListSessionsArgs validates filter input', () => {
|
||||
assert.deepStrictEqual(getListSessionsArgs({}), { session: undefined, status: undefined, workspace: undefined, withChanges: undefined, unread: undefined, withPullRequest: undefined, includeArchived: undefined, createdAfter: undefined, createdBefore: undefined });
|
||||
assert.deepStrictEqual(getListSessionsArgs({}), { session: undefined, status: undefined, workspace: undefined, withChanges: undefined, unread: undefined, withPullRequest: undefined, includeArchived: undefined, createdAfter: undefined, createdBefore: undefined, parentSession: undefined, label: undefined });
|
||||
assert.throws(() => getListSessionsArgs({ status: ['bogus'] }), /status/);
|
||||
assert.throws(() => getListSessionsArgs({ withChanges: 'yes' }), /withChanges/);
|
||||
assert.throws(() => getListSessionsArgs({ includeArchived: 'no' }), /includeArchived/);
|
||||
@@ -841,6 +944,25 @@ suite('SessionServerTools', () => {
|
||||
|
||||
// Refuses messaging the exact current chat channel (self-loop guard).
|
||||
await assert.rejects(() => applySendMessageTool(accessor, { session: 'copilot:/s1', message: 'loop' }, currentChannel), /current chat/);
|
||||
const privateChild = {
|
||||
...sessionMeta('child', SessionStatus.Idle, workspace),
|
||||
_meta: withSessionOrchestration(undefined, {
|
||||
parentSession: 'copilot:/s2',
|
||||
creatorSession: 'copilot:/s2',
|
||||
coordinateWithCreator: false,
|
||||
}),
|
||||
};
|
||||
const privateAccessor = createAccessor({
|
||||
listSessions: async () => [privateChild, sessionMeta('s2', SessionStatus.Idle, workspace)],
|
||||
});
|
||||
await assert.rejects(
|
||||
() => applySendMessageTool(privateAccessor, { session: 'copilot:/s2', message: 'blocked' }, buildDefaultChatUri('copilot:/child')),
|
||||
/not allowed to coordinate with its creator/,
|
||||
);
|
||||
await assert.rejects(
|
||||
() => applyCreateChatTool(privateAccessor, { session: 'copilot:/s2', prompt: 'blocked' }, URI.parse(buildDefaultChatUri('copilot:/child'))),
|
||||
/not allowed to coordinate with its creator/,
|
||||
);
|
||||
// Unknown session and missing session/message are rejected.
|
||||
await assert.rejects(() => applySendMessageTool(accessor, { session: 'copilot:/nope', message: 'x' }, currentChannel), /known session/);
|
||||
assert.throws(() => getSendMessageArgs({ message: 'x' }, []), /session/);
|
||||
|
||||
Reference in New Issue
Block a user