@tangle-network/chatgpt-agents-kit 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,94 @@
1
+ import { AgentFailure } from "./contracts.js";
2
+ const id = { type: 'string', pattern: '^[A-Za-z0-9_-]{1,256}$', minLength: 1, maxLength: 256 };
3
+ const properties = { workspaceId: id, q: { type: 'string', maxLength: 200 },
4
+ cursor: { type: 'string', minLength: 1, maxLength: 1536 }, limit: { type: 'integer', minimum: 1, maximum: 100 } };
5
+ const validId = (value) => typeof value === 'string' && /^[A-Za-z0-9_-]{1,256}$/.test(value);
6
+ const validDate = (value) => value === undefined || (typeof value === 'string' && Number.isFinite(Date.parse(value)));
7
+ const states = ['queued', 'working', 'input-required', 'succeeded', 'failed', 'cancelled', 'unknown'];
8
+ export function directoryTools(binding, actions, revalidate) {
9
+ return ['threads', 'tasks'].flatMap(kind => {
10
+ const action = kind === 'threads' ? 'connect' : 'task';
11
+ const maxLimit = kind === 'tasks' ? 10 : 100;
12
+ const maxCursor = kind === 'tasks' ? 1536 : 512;
13
+ const service = kind === 'threads' ? binding.services.listThreads : binding.services.listTasks;
14
+ if (!service || !actions.includes('list') || !actions.includes(action))
15
+ return [];
16
+ return [{ name: `list_my_${kind}`, description: `Choose existing ${kind} by title in an accessible workspace. Read-only; never creates or restarts work.`,
17
+ inputSchema: { type: 'object', properties: { ...properties, cursor: { ...properties.cursor, maxLength: maxCursor }, limit: { ...properties.limit, maximum: maxLimit }, ...(kind === 'tasks' ? { threadId: id } : {}) }, required: kind === 'threads' ? ['workspaceId'] : [], additionalProperties: false },
18
+ async run(args) {
19
+ const keys = [...Object.keys(properties), ...(kind === 'tasks' ? ['threadId'] : [])];
20
+ if (Object.keys(args).some(key => !keys.includes(key)) || (kind === 'threads' ? !validId(args.workspaceId) : args.workspaceId !== undefined && !validId(args.workspaceId))
21
+ || (args.threadId !== undefined && !validId(args.threadId))
22
+ || (args.q !== undefined && (typeof args.q !== 'string' || args.q.length > 200))
23
+ || (args.cursor !== undefined && (typeof args.cursor !== 'string' || !args.cursor || args.cursor.length > maxCursor))
24
+ || (args.limit !== undefined && (!Number.isSafeInteger(args.limit) || Number(args.limit) < 1 || Number(args.limit) > maxLimit)))
25
+ throw new AgentFailure('invalid_argument');
26
+ const query = args;
27
+ const native = async (read) => {
28
+ try {
29
+ return await read();
30
+ }
31
+ catch (error) {
32
+ throw new AgentFailure(error instanceof AgentFailure && [401, 403, 404].includes(error.status) ? 'native_target_forbidden' : 'native_directory_unavailable', error instanceof AgentFailure && [401, 403, 404].includes(error.status) ? error.status : 503);
33
+ }
34
+ };
35
+ const visibleWorkspaces = async () => {
36
+ const workspaces = await native(() => binding.services.list());
37
+ await revalidate();
38
+ if (query.workspaceId && !workspaces.some(workspace => workspace.workspaceId === query.workspaceId))
39
+ throw new AgentFailure('native_target_forbidden', 403);
40
+ return new Set(workspaces.map(workspace => workspace.workspaceId));
41
+ };
42
+ const before = await visibleWorkspaces();
43
+ await revalidate();
44
+ const page = kind === 'threads' ? await native(() => binding.services.listThreads(query)) : await native(() => binding.services.listTasks(query));
45
+ const after = await visibleWorkspaces();
46
+ if (!page || typeof page !== 'object')
47
+ throw new AgentFailure('native_directory_invalid', 502);
48
+ const rows = kind === 'threads' ? ('threads' in page ? page.threads : undefined) : ('tasks' in page ? page.tasks : undefined);
49
+ if (!Array.isArray(rows) || rows.length > (query.limit ?? maxLimit)
50
+ || (page.nextCursor !== null && (typeof page.nextCursor !== 'string' || !page.nextCursor || page.nextCursor.length > maxCursor)))
51
+ throw new AgentFailure('native_directory_invalid', 502);
52
+ const projected = [];
53
+ const targets = [];
54
+ for (const value of rows) {
55
+ if (!value || typeof value !== 'object' || Array.isArray(value))
56
+ throw new AgentFailure('native_directory_invalid', 502);
57
+ const row = value;
58
+ if (!row || !validId(row.threadId) || typeof row.title !== 'string' || row.title.length > 1024 || !validDate(row.updatedAt))
59
+ throw new AgentFailure('native_directory_invalid', 502);
60
+ const workspaceId = kind === 'tasks' ? row.workspaceId : query.workspaceId;
61
+ if (!validId(workspaceId) || !before.has(workspaceId) || !after.has(workspaceId) || (query.workspaceId && workspaceId !== query.workspaceId))
62
+ throw new AgentFailure('native_target_forbidden', 403);
63
+ const target = { workspaceId, threadId: row.threadId };
64
+ targets.push(target);
65
+ if (query.threadId && row.threadId !== query.threadId)
66
+ throw new AgentFailure('native_directory_invalid', 502);
67
+ await native(() => binding.authorize(target, action));
68
+ await revalidate();
69
+ const summary = { threadId: row.threadId, title: row.title, ...(row.updatedAt ? { updatedAt: row.updatedAt } : {}) };
70
+ if (kind === 'tasks') {
71
+ if (!validId(row.turnId) || (typeof row.state !== 'string' || !states.includes(row.state)) || !validDate(row.completedAt) || typeof row.completionVerified !== 'boolean')
72
+ throw new AgentFailure('native_directory_invalid', 502);
73
+ for (const name of [row.workspaceName, row.threadTitle])
74
+ if (name !== undefined && (typeof name !== 'string' || name.length > 1024))
75
+ throw new AgentFailure('native_directory_invalid', 502);
76
+ if (!validDate(row.createdAt))
77
+ throw new AgentFailure('native_directory_invalid', 502);
78
+ projected.push({ ...summary, ...target, turnId: row.turnId, state: row.state, completionVerified: row.completionVerified === true, ...(row.statusError ? { statusError: 'status_unavailable' } : {}), ...(row.workspaceName ? { workspaceName: row.workspaceName } : {}), ...(row.threadTitle ? { threadTitle: row.threadTitle } : {}), ...(row.createdAt ? { createdAt: row.createdAt } : {}), ...(row.completedAt ? { completedAt: row.completedAt } : {}) });
79
+ }
80
+ else if (kind === 'threads')
81
+ projected.push(summary);
82
+ else
83
+ throw new AgentFailure('native_directory_invalid', 502);
84
+ }
85
+ // Authorization can change while later rows await native services.
86
+ await visibleWorkspaces();
87
+ for (const target of targets)
88
+ await native(() => binding.authorize(target, action));
89
+ await revalidate();
90
+ return { appName: binding.appName, identityId: binding.applicationUserId, ...(query.workspaceId ? { workspaceId: query.workspaceId } : {}),
91
+ ...(query.threadId ? { threadId: query.threadId } : {}), [kind]: projected, nextCursor: page.nextCursor };
92
+ } }];
93
+ });
94
+ }
package/dist/src/gtm.d.ts CHANGED
@@ -12,7 +12,10 @@ export interface GtmNativeContext {
12
12
  request(path: string, init: RequestInit): Promise<Response>;
13
13
  /** Use the persisted agent-app turn path. HTTP 200 alone is NOT admission. */
14
14
  turns?: Pick<NativeServices, 'startTask' | 'getTask'>;
15
+ modelSelection?: NativeBinding['modelSelection'];
15
16
  interactions?: Pick<NativeServices, 'pendingDecisions' | 'decide'>;
17
+ /** Native paginated discovery; absent services expose no discovery tools. */
18
+ directory?: Pick<NativeServices, 'listThreads' | 'listTasks'>;
16
19
  /** Only supply a live SAME-workspace application-line adapter, not createHostedAgent's legacy per-person sandbox path. */
17
20
  channels?: Pick<NativeServices, 'channelBinding' | 'sendChannel'>;
18
21
  }
package/dist/src/gtm.js CHANGED
@@ -69,13 +69,14 @@ export function createGtmBinding(context) {
69
69
  throw new AgentFailure('gtm_binary_output_unsupported', 415);
70
70
  return { content: result.file.content, revision, mediaType };
71
71
  },
72
+ ...context.directory,
72
73
  ...context.turns,
73
74
  ...context.interactions,
74
75
  ...context.channels,
75
76
  };
76
77
  return {
77
78
  platformUserId: context.platformUserId, applicationUserId: context.applicationUserId,
78
- appName: 'tangle-agents-gtm', authorize: context.authorize, services,
79
+ appName: 'tangle-agents-gtm', authorize: context.authorize, services, modelSelection: context.modelSelection,
79
80
  // These are GTM's reviewed native scopes. Do NOT reinterpret Hub's `read`
80
81
  // or `workflows:write` token as these permissions; ungranted actions cannot run.
81
82
  requiredScopes: { list: read, connect: read, create: write, handoff: write, delegate: run, task: read, output: read, decisions: read, decide: run },
@@ -1,7 +1,7 @@
1
1
  export { createAgentsHandler } from './kit.ts';
2
2
  export { createGtmBinding } from './gtm.ts';
3
3
  export { AgentFailure } from './contracts.ts';
4
- export type { Action, Metadata, NativeBinding, NativeChannel, NativeFile, NativeMcpFactory, NativeServices, NativeTask, NativeResponse, NativeConnection, NativeEnvironment, OAuthResource, Principal, Target, } from './contracts.ts';
4
+ export type { Action, Metadata, NativeBinding, NativeChannel, NativeFile, NativeMcpFactory, NativeServices, NativeTask, NativeTaskOptions, NativeResponse, NativeConnection, NativeEnvironment, OAuthResource, Principal, Target, NativeDirectoryQuery, NativeTaskQuery, NativeThreadSummary, NativeTaskSummary, } from './contracts.ts';
5
5
  export type { GtmNativeContext } from './gtm.ts';
6
6
  export type { InspectionBinding, InspectionExposure } from './inspection.ts';
7
7
  export { createTaskEvents, TASK_EVENT } from './task-events.ts';
package/dist/src/kit.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AGENT_CARDS_URI, AGENT_CARDS_MIME, AGENT_CARD_TOOLS } from "./cards.js";
2
+ import { directoryTools } from "./directory.js";
2
3
  import { cardOutputSchemas } from "./card-output-schemas.js";
3
4
  import { agentAppDiscovery, defineAgentAppMetadata } from "./app-metadata.js";
4
5
  import { AgentFailure } from "./contracts.js";
@@ -106,7 +107,8 @@ async function output(binding, target, path) {
106
107
  async function taskReceipt(binding, target, task, expectedTurn, canReadOutput, checkOutput) {
107
108
  validateNativeTask(task, target, expectedTurn);
108
109
  const base = { ...target, turnId: task.turnId, executionId: task.executionId, state: task.state, source: task.source, ...(task.environment ? { environment: projectEnvironment(task.environment) } : {}) };
109
- if (task.state !== 'succeeded')
110
+ const settledResponse = ['failed', 'input-required'].includes(task.state) && task.response && task.completedAt;
111
+ if (task.state !== 'succeeded' && !settledResponse)
110
112
  return { ...base, completionVerified: false };
111
113
  if (!task.executionId || !task.completedAt || !Number.isFinite(Date.parse(task.completedAt)) || (!task.outputs?.length && !task.response))
112
114
  return { ...base, completionVerified: false, reason: 'execution_output_provenance_missing' };
@@ -133,18 +135,24 @@ async function taskReceipt(binding, target, task, expectedTurn, canReadOutput, c
133
135
  if (bytes > 262144)
134
136
  throw new AgentFailure('output_too_large', 413);
135
137
  outcome = value.outcome ?? 'completed';
138
+ if (task.state !== 'succeeded' && outcome !== task.state)
139
+ throw new AgentFailure('native_response_outcome_mismatch', 502);
136
140
  // Explicit projection: do not expose arbitrary host/provider fields.
137
141
  response = { content: value.content, mediaType: value.mediaType, sha256: await digest(value.content), bytes };
138
142
  }
139
- if (task.outputs?.length && !binding.services.readFile)
140
- return { ...base, completionVerified: false, reason: 'output_reader_unavailable' };
143
+ const unavailableOutput = (reason) => ({ ...base, completionVerified: false, reason,
144
+ ...(outcome !== 'completed' && response ? { state: outcome, response, completedAt: task.completedAt, outputs: [] } : {}) });
145
+ if (task.outputs?.length && !binding.services.readFile) {
146
+ await checkOutput();
147
+ return unavailableOutput('output_reader_unavailable');
148
+ }
141
149
  const outputs = [];
142
150
  for (const ref of task.outputs ?? []) {
143
151
  await checkOutput();
144
152
  const file = await output(binding, target, ref.path);
145
153
  await checkOutput();
146
154
  if (!ref.revision || file.revision !== ref.revision)
147
- return { ...base, completionVerified: false, reason: 'output_revision_changed' };
155
+ return unavailableOutput('output_revision_changed');
148
156
  outputs.push(file);
149
157
  }
150
158
  return { ...base, ...(outcome !== 'completed' ? { state: outcome, reason: 'native_result_not_completed' } : {}),
@@ -217,7 +225,10 @@ async function run(action, args, binding, revalidate, signal, policy, canReadTas
217
225
  throw new PreAdmissionFailure(error);
218
226
  }
219
227
  try {
220
- const task = await s.startTask(target, args.turnId, args.prompt, args.contextVersion === undefined ? undefined : { contextVersion: args.contextVersion });
228
+ const task = await s.startTask(target, args.turnId, args.prompt, args.contextVersion === undefined && args.model === undefined ? undefined : {
229
+ ...(args.contextVersion === undefined ? {} : { contextVersion: args.contextVersion }),
230
+ ...(args.model === undefined ? {} : { model: args.model }),
231
+ });
221
232
  result = await taskReceipt(binding, target, await observe(task, policy.defaultMs), args.turnId, canReadOutput, checkOutput);
222
233
  break;
223
234
  }
@@ -459,15 +470,20 @@ export function createAgentsHandler(options) {
459
470
  _meta: { ui: { prefersBorder: true, csp: { connectDomains: [], resourceDomains: [] }, ...(options.cards.domain ? { domain: options.cards.domain } : {}) } },
460
471
  }], ...(v2 ? { resultType: 'complete' } : {}) } });
461
472
  }
473
+ const actionSchemas = { ...schemas, delegate: binding.modelSelection
474
+ ? { ...schemas.delegate, model: { ...str, optional: true, description: 'Model for this task only. The app validates availability and permissions; omission preserves its saved default. Changing model requires a new turnId.' } }
475
+ : schemas.delegate };
476
+ const directory = directoryTools(binding, actions, revalidate);
477
+ const executableTools = [...actions.map(action => names[action]), ...directory.map(tool => tool.name)];
462
478
  const workflows = describeAgentWorkflows(actions);
463
479
  const handoff = workflows.some(workflow => workflow.id === 'handoff-to-agent') ? createHandoffTool(binding, revalidate) : null;
464
480
  const profileSchema = { type: 'object', properties: { id: { type: 'string', minLength: 1 }, name: { type: 'string' } }, required: ['id'], additionalProperties: false };
465
481
  const tools = [
466
482
  { name: 'agent_profile', description: 'Stable application identity of the authenticated account.', inputSchema: { type: 'object', properties: {}, additionalProperties: false }, run: async (args) => { validate(args, {}); return { id: binding.applicationUserId, ...(binding.name ? { name: binding.name } : {}) }; } },
467
- { name: 'agent_capabilities', description: 'Discover actions actually supported for this identity, app and grant now.', inputSchema: { type: 'object', properties: {}, additionalProperties: false }, run: async (args) => { validate(args, {}); return { appName: binding.appName, actions: [...actions.map(a => names[a]), ...(handoff ? [handoff.name] : [])], workflows, inspection: inspection?.tools.map(tool => tool.name) ?? [], media: ['text/plain; charset=utf-8'], compatibilityAliases: actions.includes('delegate') ? { delegate_task: names.delegate } : {}, preferredPromptTool: actions.includes('delegate') ? names.delegate : null, resultKinds: canReadOutput ? [...(binding.services.readFile ? ['files'] : []), ...(binding.services.readResponse ? ['response'] : [])] : [], execution: 'native-application', stateStore: 'native-application', taskWait: { defaultMs: waitPolicy.defaultMs, maxMs: waitPolicy.maxMs }, events: eventsEnabled ? { name: 'agent.task_updated', protocolVersion: '2026-07-28', delivery: ['webhook'], replay: false } : null }; } },
468
- ...advertisedActions.map(action => ({ name: names[action], description: descriptions[action], inputSchema: { type: 'object', properties: Object.fromEntries(Object.entries(schemas[action]).map(([key, value]) => { const { optional: _optional, ...schema } = value; return [key, schema]; })), required: Object.keys(schemas[action]).filter(key => !schemas[action][key].optional), additionalProperties: false }, run: async (args) => {
483
+ { name: 'agent_capabilities', description: 'Discover actions actually supported for this identity, app and grant now.', inputSchema: { type: 'object', properties: {}, additionalProperties: false }, run: async (args) => { validate(args, {}); return { appName: binding.appName, actions: [...executableTools, ...(handoff ? [handoff.name] : [])], workflows, inspection: inspection?.tools.map(tool => tool.name) ?? [], media: ['text/plain; charset=utf-8'], compatibilityAliases: actions.includes('delegate') ? { delegate_task: names.delegate } : {}, preferredPromptTool: actions.includes('delegate') ? names.delegate : null, resultKinds: canReadOutput ? [...(binding.services.readFile ? ['files'] : []), ...(binding.services.readResponse ? ['response'] : [])] : [], execution: 'native-application', stateStore: 'native-application', taskWait: { defaultMs: waitPolicy.defaultMs, maxMs: waitPolicy.maxMs }, events: eventsEnabled ? { name: 'agent.task_updated', protocolVersion: '2026-07-28', delivery: ['webhook'], replay: false } : null }; } },
484
+ ...advertisedActions.map(action => ({ name: names[action], description: descriptions[action], inputSchema: { type: 'object', properties: Object.fromEntries(Object.entries(actionSchemas[action]).map(([key, value]) => { const { optional: _optional, ...schema } = value; return [key, schema]; })), required: Object.keys(actionSchemas[action]).filter(key => !actionSchemas[action][key].optional), additionalProperties: false }, run: async (args) => {
469
485
  try {
470
- validate(args, schemas[action]);
486
+ validate(args, actionSchemas[action]);
471
487
  }
472
488
  catch (error) {
473
489
  throw action === 'delegate' ? new PreAdmissionFailure(error) : error;
@@ -481,6 +497,7 @@ export function createAgentsHandler(options) {
481
497
  ? { ...result, notification: { state: 'not_subscribed', name: 'agent.task_updated', arguments: { workspaceId: args.workspaceId, threadId: args.threadId, turnId: args.turnId } } }
482
498
  : result;
483
499
  } })),
500
+ ...directory,
484
501
  ...(handoff ? [handoff] : []),
485
502
  ...(inspection?.tools ?? []),
486
503
  ];
@@ -504,7 +521,7 @@ export function createAgentsHandler(options) {
504
521
  const failed = result && typeof result === 'object' && failureMarker in result ? result[failureMarker] : null;
505
522
  return failed
506
523
  ? { isError: true, content: [{ type: 'text', text: failed.code }], structuredContent: { error: failed } }
507
- : { content: [{ type: 'text', text: JSON.stringify(result) }], structuredContent: result, ...(options.cards && AGENT_CARD_TOOLS.includes(tool.name) ? { _meta: { agentCards: { displayName: metadata.displayName, tools: actions.map(action => names[action]) } } } : {}) };
524
+ : { content: [{ type: 'text', text: JSON.stringify(result) }], structuredContent: result, ...(options.cards && AGENT_CARD_TOOLS.includes(tool.name) ? { _meta: { agentCards: { displayName: metadata.displayName, applicationOrigin: metadata.applicationOrigin, tools: executableTools } } } : {}) };
508
525
  },
509
526
  });
510
527
  if (parsed.method === 'tools/call' && record(parsed.params) && parsed.params.name === 'delegate_task') {
@@ -526,7 +543,8 @@ export function createAgentsHandler(options) {
526
543
  const handoffScopes = tool.name === HANDOFF_TOOL_NAME
527
544
  ? [...new Set(HANDOFF_ACTIONS.flatMap(a => binding.requiredScopes[a] ?? []))]
528
545
  : [];
529
- const securitySchemes = [{ type: 'oauth2', scopes: inspectionTool ? inspectionScopes : action ? binding.requiredScopes[action] : handoffScopes }];
546
+ const directoryScopes = directory.some(item => item.name === tool.name) ? [...new Set([...(binding.requiredScopes.list ?? []), ...(binding.requiredScopes[tool.name === 'list_my_threads' ? 'connect' : 'task'] ?? [])])] : null;
547
+ const securitySchemes = [{ type: 'oauth2', scopes: directoryScopes ?? (inspectionTool ? inspectionScopes : action ? binding.requiredScopes[action] : handoffScopes) }];
530
548
  Object.assign(tool, { annotations: { readOnlyHint: readOnly, destructiveHint: !readOnly, idempotentHint: readOnly, openWorldHint: action === 'delegate' || action === 'decide' || action === 'channel' }, securitySchemes, _meta: { securitySchemes, ...(tool.name === 'agent_profile' ? { 'openai/profile': true } : {}), ...(options.cards && AGENT_CARD_TOOLS.includes(String(tool.name)) ? { ui: { resourceUri: AGENT_CARDS_URI, visibility: ['model', 'app'] } } : {}) }, ...(tool.name === 'agent_profile' ? { outputSchema: profileSchema } : {}), ...(Object.hasOwn(cardOutputSchemas, String(tool.name)) ? { outputSchema: cardOutputSchemas[tool.name] } : {}) });
531
549
  }
532
550
  if (v2 && envelope.result) {
@@ -3,7 +3,7 @@ import { dirname, join, resolve } from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
4
  import { parseArgs } from 'node:util';
5
5
  import { AGENT_APP_METADATA_KEY } from "./app-metadata.js";
6
- import { ACTION_TOOLS, selectAgentWorkflows, describeAgentWorkflows } from "./workflows.js";
6
+ import { ACTION_TOOLS, selectAgentWorkflows, describeAgentWorkflows, selectAgentStarterPrompts } from "./workflows.js";
7
7
  export function endpointUrl(value, allowLoopback = false) {
8
8
  const url = new URL(value);
9
9
  if (url.username || url.password || url.search || url.hash || (url.protocol !== 'https:' && !(allowLoopback && url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname))))
@@ -159,7 +159,7 @@ async function writePackage(m, url, out, discovered, connectionId) {
159
159
  await mkdir(join(temporary, 'skills/operate-existing-agent'), { recursive: true });
160
160
  await writeFile(join(temporary, 'plugin.json'), JSON.stringify({
161
161
  $schema: 'https://agent-plugins.org/schemas/1.0.0/plugin.schema.json', name: m.name, version: '0.1.0', description: m.description,
162
- extensions: { 'com.openai': { ...(connectionId ? { apps: './.app.json' } : {}), interface: { displayName: m.displayName, shortDescription: m.description, developerName: 'Tangle', category: 'Productivity', websiteURL: m.applicationOrigin, defaultPrompt: ['Connect my agent', ...workflows.slice(0, 2).map(workflow => workflow.prompt)] } } },
162
+ extensions: { 'com.openai': { ...(connectionId ? { apps: './.app.json' } : {}), interface: { displayName: m.displayName, shortDescription: m.description, developerName: 'Tangle', category: 'Productivity', websiteURL: m.applicationOrigin, defaultPrompt: selectAgentStarterPrompts(m.actions) } } },
163
163
  }, null, 2) + '\n');
164
164
  if (connectionId)
165
165
  await writeFile(join(temporary, '.app.json'), JSON.stringify({ apps: { agents: { id: connectionId } } }, null, 2) + '\n');
@@ -188,7 +188,10 @@ async function writePackage(m, url, out, discovered, connectionId) {
188
188
  ? `4. Verify that .app.json maps this endpoint's registered connection: ${connectionId}. This mapping is developer-supplied, not verified by the generator.`
189
189
  : '4. Copy the real registered connection ID from ChatGPT. Rerun the installed generator with --connection-id and a new --out directory to add its .app.json mapping.',
190
190
  '5. Import/install that generated package using the current ChatGPT plugin instructions; test in a new chat.',
191
- '6. Ask: Connect my agent. Select the SAME existing agent, workspace and conversation used in the app.', '',
191
+ '6. Ask for the agent or project by name. Choose the existing conversation when several matches appear.', '',
192
+ 'Describe the result you want, review it here, then ask for a specific change in the same conversation.',
193
+ 'Return later by asking for that work by name. Available discovery tools determine which work ChatGPT can find.', '',
194
+ '## Developer acceptance', '',
192
195
  'Prompt the selected agent; use get_task to observe the same turn.',
193
196
  'Reopen results with read_output and the returned native references.',
194
197
  'A queued receipt is not completion. Missing capabilities stay unavailable.', '',
@@ -17,3 +17,5 @@ export declare function describeAgentWorkflows(actions: readonly Action[]): {
17
17
  prompt: string;
18
18
  requiredTools: string[];
19
19
  }[];
20
+ /** Starters describe only packaged actions; live discovery still decides what can run. */
21
+ export declare function selectAgentStarterPrompts(actions: readonly Action[]): string[];
@@ -8,16 +8,16 @@ export const HANDOFF_ACTIONS = ['connect', 'handoff', 'output', 'delegate', 'tas
8
8
  /** These compose existing actions. They neither register tools nor grant access. */
9
9
  export const AGENT_WORKFLOWS = [
10
10
  { id: 'handoff-to-agent', title: 'Hand this work to my agent',
11
- prompt: 'Prepare a reviewed handoff of this work to my existing agent.',
11
+ prompt: 'Help my agent finish this work using the context we have chosen.',
12
12
  requires: HANDOFF_ACTIONS },
13
13
  { id: 'review-agent-deliverables', title: 'Review and improve my agent’s work',
14
- prompt: 'Retrieve my agent’s actual deliverables and help me review and revise them.',
14
+ prompt: 'Review my agent’s latest result and help me improve it.',
15
15
  requires: ['connect', 'task', 'delegate'] },
16
16
  { id: 'save-agent-playbook', title: 'Save this workflow for my agent',
17
- prompt: 'Turn this successful workflow into a reusable playbook for my agent to review.',
17
+ prompt: 'Save how we did this as a playbook for my agent.',
18
18
  requires: ['connect', 'handoff', 'output'] },
19
19
  { id: 'resume-agent-work', title: 'Pick up where my agent left off',
20
- prompt: 'Reconnect to the same agent and task, and show verified results and remaining work.',
20
+ prompt: 'Pick up my recent work and show what needs my attention.',
21
21
  requires: ['connect', 'task'] },
22
22
  { id: 'resolve-agent-decisions', title: 'Resolve my agent’s pending decisions',
23
23
  prompt: 'Show the pending decisions for my selected agent so I can review them.',
@@ -36,3 +36,21 @@ export function describeAgentWorkflows(actions) {
36
36
  id, title, prompt, requiredTools: requires.map(action => ACTION_TOOLS[action]),
37
37
  }));
38
38
  }
39
+ /** Starters describe only packaged actions; live discovery still decides what can run. */
40
+ export function selectAgentStarterPrompts(actions) {
41
+ const available = new Set(actions);
42
+ const prompts = [];
43
+ if (available.has('list'))
44
+ prompts.push('Show my agents and help me choose what to work on.');
45
+ if (available.has('connect') && available.has('delegate')) {
46
+ prompts.push('Help my agent finish a task in its existing workspace.');
47
+ }
48
+ const workflows = selectAgentWorkflows(actions);
49
+ const resume = workflows.find(workflow => workflow.id === 'resume-agent-work');
50
+ if (resume)
51
+ prompts.push(resume.prompt);
52
+ for (const workflow of workflows)
53
+ if (!prompts.includes(workflow.prompt))
54
+ prompts.push(workflow.prompt);
55
+ return prompts.length ? prompts.slice(0, 3) : ['Show what this agent connection can do.'];
56
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/chatgpt-agents-kit",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/tangle-network/chatgpt-plugins.git",