@jungjaehoon/mama-os 0.49.3 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent-loop.d.ts +5 -1
- package/dist/agent/agent-loop.d.ts.map +1 -1
- package/dist/agent/agent-loop.js +22 -3
- package/dist/agent/agent-loop.js.map +1 -1
- package/dist/agent/code-act/host-bridge.d.ts.map +1 -1
- package/dist/agent/code-act/host-bridge.js +39 -7
- package/dist/agent/code-act/host-bridge.js.map +1 -1
- package/dist/agent/codex-app-server-process.d.ts.map +1 -1
- package/dist/agent/codex-app-server-process.js +37 -0
- package/dist/agent/codex-app-server-process.js.map +1 -1
- package/dist/agent/gateway-tool-executor.d.ts +17 -6
- package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
- package/dist/agent/gateway-tool-executor.js +512 -143
- package/dist/agent/gateway-tool-executor.js.map +1 -1
- package/dist/agent/gateway-tools.md +7 -6
- package/dist/agent/native-effect-observer.d.ts +19 -0
- package/dist/agent/native-effect-observer.d.ts.map +1 -0
- package/dist/agent/native-effect-observer.js +70 -0
- package/dist/agent/native-effect-observer.js.map +1 -0
- package/dist/agent/persistent-cli-process.d.ts.map +1 -1
- package/dist/agent/persistent-cli-process.js +6 -1
- package/dist/agent/persistent-cli-process.js.map +1 -1
- package/dist/agent/tool-registry.d.ts.map +1 -1
- package/dist/agent/tool-registry.js +15 -8
- package/dist/agent/tool-registry.js.map +1 -1
- package/dist/agent/types.d.ts +8 -2
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/api/report-handler.d.ts +19 -3
- package/dist/api/report-handler.d.ts.map +1 -1
- package/dist/api/report-handler.js +123 -17
- package/dist/api/report-handler.js.map +1 -1
- package/dist/api/report-persistence.d.ts +3 -3
- package/dist/api/report-persistence.d.ts.map +1 -1
- package/dist/api/report-persistence.js +11 -21
- package/dist/api/report-persistence.js.map +1 -1
- package/dist/cli/commands/start.d.ts +4 -0
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +49 -157
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/runtime/agent-loop-init.d.ts.map +1 -1
- package/dist/cli/runtime/agent-loop-init.js +1 -0
- package/dist/cli/runtime/agent-loop-init.js.map +1 -1
- package/dist/cli/runtime/api-routes-init.d.ts +5 -19
- package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
- package/dist/cli/runtime/api-routes-init.js +7 -79
- package/dist/cli/runtime/api-routes-init.js.map +1 -1
- package/dist/db/migrations/operator-owner-action-candidates.d.ts +19 -0
- package/dist/db/migrations/operator-owner-action-candidates.d.ts.map +1 -0
- package/dist/db/migrations/operator-owner-action-candidates.js +254 -0
- package/dist/db/migrations/operator-owner-action-candidates.js.map +1 -0
- package/dist/db/migrations/operator-task-external-lifecycle.d.ts +9 -0
- package/dist/db/migrations/operator-task-external-lifecycle.d.ts.map +1 -1
- package/dist/db/migrations/operator-task-external-lifecycle.js +52 -22
- package/dist/db/migrations/operator-task-external-lifecycle.js.map +1 -1
- package/dist/db/migrations/operator-task-review-lifecycle.d.ts.map +1 -1
- package/dist/db/migrations/operator-task-review-lifecycle.js +3 -0
- package/dist/db/migrations/operator-task-review-lifecycle.js.map +1 -1
- package/dist/db/migrations/owner-action-effects.d.ts +18 -0
- package/dist/db/migrations/owner-action-effects.d.ts.map +1 -0
- package/dist/db/migrations/owner-action-effects.js +111 -0
- package/dist/db/migrations/owner-action-effects.js.map +1 -0
- package/dist/gateways/message-router.d.ts.map +1 -1
- package/dist/gateways/message-router.js +14 -4
- package/dist/gateways/message-router.js.map +1 -1
- package/dist/gateways/telegram-text-entities.d.ts +99 -0
- package/dist/gateways/telegram-text-entities.d.ts.map +1 -0
- package/dist/gateways/telegram-text-entities.js +189 -0
- package/dist/gateways/telegram-text-entities.js.map +1 -0
- package/dist/gateways/telegram.d.ts.map +1 -1
- package/dist/gateways/telegram.js +13 -2
- package/dist/gateways/telegram.js.map +1 -1
- package/dist/gateways/types.d.ts +8 -0
- package/dist/gateways/types.d.ts.map +1 -1
- package/dist/operator/board-read-views.d.ts +9 -4
- package/dist/operator/board-read-views.d.ts.map +1 -1
- package/dist/operator/board-read-views.js +11 -0
- package/dist/operator/board-read-views.js.map +1 -1
- package/dist/operator/external-lifecycle-candidates.d.ts +15 -1
- package/dist/operator/external-lifecycle-candidates.d.ts.map +1 -1
- package/dist/operator/external-lifecycle-candidates.js +164 -0
- package/dist/operator/external-lifecycle-candidates.js.map +1 -1
- package/dist/operator/external-lifecycle-discovery.d.ts +61 -0
- package/dist/operator/external-lifecycle-discovery.d.ts.map +1 -0
- package/dist/operator/external-lifecycle-discovery.js +103 -0
- package/dist/operator/external-lifecycle-discovery.js.map +1 -0
- package/dist/operator/external-lifecycle.d.ts +23 -0
- package/dist/operator/external-lifecycle.d.ts.map +1 -1
- package/dist/operator/owner-action-effects.d.ts +101 -0
- package/dist/operator/owner-action-effects.d.ts.map +1 -0
- package/dist/operator/owner-action-effects.js +335 -0
- package/dist/operator/owner-action-effects.js.map +1 -0
- package/dist/operator/owner-event-inbox.d.ts +1 -0
- package/dist/operator/owner-event-inbox.d.ts.map +1 -1
- package/dist/operator/owner-event-inbox.js +6 -0
- package/dist/operator/owner-event-inbox.js.map +1 -1
- package/dist/operator/owner-event-loop.d.ts +3 -0
- package/dist/operator/owner-event-loop.d.ts.map +1 -1
- package/dist/operator/owner-event-loop.js +34 -0
- package/dist/operator/owner-event-loop.js.map +1 -1
- package/dist/operator/owner-event-outcome.d.ts.map +1 -1
- package/dist/operator/owner-event-outcome.js +6 -0
- package/dist/operator/owner-event-outcome.js.map +1 -1
- package/dist/operator/owner-event-policy.d.ts.map +1 -1
- package/dist/operator/owner-event-policy.js +2 -26
- package/dist/operator/owner-event-policy.js.map +1 -1
- package/dist/operator/owner-event-prompt.d.ts.map +1 -1
- package/dist/operator/owner-event-prompt.js +2 -5
- package/dist/operator/owner-event-prompt.js.map +1 -1
- package/dist/operator/task-ledger.d.ts +93 -14
- package/dist/operator/task-ledger.d.ts.map +1 -1
- package/dist/operator/task-ledger.js +430 -103
- package/dist/operator/task-ledger.js.map +1 -1
- package/dist/operator/temporal-reconcile.d.ts.map +1 -1
- package/dist/operator/temporal-reconcile.js +6 -2
- package/dist/operator/temporal-reconcile.js.map +1 -1
- package/dist/operator/workorder-consumer.d.ts +2 -0
- package/dist/operator/workorder-consumer.d.ts.map +1 -1
- package/dist/operator/workorder-consumer.js +20 -5
- package/dist/operator/workorder-consumer.js.map +1 -1
- package/dist/scheduler/heartbeat.d.ts.map +1 -1
- package/dist/scheduler/heartbeat.js +1 -0
- package/dist/scheduler/heartbeat.js.map +1 -1
- package/dist/sqlite.d.ts +1 -1
- package/dist/sqlite.d.ts.map +1 -1
- package/dist/sqlite.js +3 -2
- package/dist/sqlite.js.map +1 -1
- package/dist/wiki/wiki-read.d.ts +2 -1
- package/dist/wiki/wiki-read.d.ts.map +1 -1
- package/dist/wiki/wiki-read.js +13 -2
- package/dist/wiki/wiki-read.js.map +1 -1
- package/dist/wiki/wiki-turn-contract.js +1 -1
- package/dist/wiki/wiki-turn-contract.js.map +1 -1
- package/package.json +1 -1
- package/public/viewer/operator/operator.js +3 -1
|
@@ -87,6 +87,8 @@ const wiki_read_js_1 = require("../wiki/wiki-read.js");
|
|
|
87
87
|
const changes_projection_js_1 = require("../operator/changes-projection.js");
|
|
88
88
|
const task_list_views_js_1 = require("../operator/task-list-views.js");
|
|
89
89
|
const board_read_views_js_1 = require("../operator/board-read-views.js");
|
|
90
|
+
const owner_action_effects_js_1 = require("../operator/owner-action-effects.js");
|
|
91
|
+
const external_lifecycle_discovery_js_1 = require("../operator/external-lifecycle-discovery.js");
|
|
90
92
|
const read_js_1 = require("../evidence/read.js");
|
|
91
93
|
const private_connector_policy_js_1 = require("../connectors/private-connector-policy.js");
|
|
92
94
|
const member_candidate_store_js_1 = require("../gateways/member-candidate-store.js");
|
|
@@ -113,7 +115,7 @@ function bindTemporalContextPacketTask(context, task) {
|
|
|
113
115
|
function temporalIdentifierRef(value) {
|
|
114
116
|
return `sha256:${(0, crypto_1.createHash)('sha256').update(value).digest('hex')}`;
|
|
115
117
|
}
|
|
116
|
-
function temporalPacketRawSourcesWithinBoundSource(context, sourceRefs, reviewWindow) {
|
|
118
|
+
function temporalPacketRawSourcesWithinBoundSource(context, sourceRefs, reviewWindow, additionalVisible = () => false) {
|
|
117
119
|
const rawRefs = sourceRefs.filter((value) => typeof value === 'object' &&
|
|
118
120
|
value !== null &&
|
|
119
121
|
!Array.isArray(value) &&
|
|
@@ -121,29 +123,23 @@ function temporalPacketRawSourcesWithinBoundSource(context, sourceRefs, reviewWi
|
|
|
121
123
|
if (!context.sourceChannel) {
|
|
122
124
|
return rawRefs.length === 0;
|
|
123
125
|
}
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
126
|
+
const channelMatches = (ref) => typeof ref.connector === 'string' &&
|
|
127
|
+
typeof ref.channel_id === 'string' &&
|
|
128
|
+
temporalIdentifierRef(`${ref.connector}:${ref.channel_id}`) === context.sourceChannel;
|
|
129
|
+
const anchorMatches = (ref) => channelMatches(ref) &&
|
|
130
|
+
Boolean(context.sourceEventId &&
|
|
131
|
+
[ref.raw_id, ref.source_id].some((value) => typeof value === 'string' && temporalIdentifierRef(value) === context.sourceEventId));
|
|
132
|
+
return (rawRefs.every((ref) => (channelMatches(ref) &&
|
|
133
|
+
(reviewWindow !== undefined || !context.sourceEventId || anchorMatches(ref))) ||
|
|
134
|
+
additionalVisible(ref)) &&
|
|
135
|
+
((rawRefs.length === 0 && reviewWindow === undefined) ||
|
|
136
|
+
!context.sourceEventId ||
|
|
137
|
+
rawRefs.some(anchorMatches)));
|
|
136
138
|
}
|
|
137
|
-
function temporalPacketReferencesBoundSource(context, sourceRefs, reviewWindow) {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
value.kind === 'raw');
|
|
142
|
-
if (context.sourceEventId || context.sourceChannel) {
|
|
143
|
-
return (hasRawReference &&
|
|
144
|
-
temporalPacketRawSourcesWithinBoundSource(context, sourceRefs, reviewWindow));
|
|
145
|
-
}
|
|
146
|
-
return temporalPacketRawSourcesWithinBoundSource(context, sourceRefs, reviewWindow);
|
|
139
|
+
function temporalPacketReferencesBoundSource(context, sourceRefs, reviewWindow, additionalVisible) {
|
|
140
|
+
const hasRaw = sourceRefs.some((ref) => typeof ref === 'object' && ref !== null && ref.kind === 'raw');
|
|
141
|
+
return ((!(context.sourceEventId || context.sourceChannel) || hasRaw) &&
|
|
142
|
+
temporalPacketRawSourcesWithinBoundSource(context, sourceRefs, reviewWindow, additionalVisible));
|
|
147
143
|
}
|
|
148
144
|
function temporalPacketHasExactReviewWindow(packetJson, reviewWindow) {
|
|
149
145
|
try {
|
|
@@ -232,6 +228,7 @@ const TASK_UPDATE_PUBLIC_FIELD_SET = new Set(TASK_UPDATE_PUBLIC_FIELDS);
|
|
|
232
228
|
*/
|
|
233
229
|
const TASK_CREATE_PUBLIC_FIELDS = [
|
|
234
230
|
'title',
|
|
231
|
+
'creation_key',
|
|
235
232
|
'completion_criteria',
|
|
236
233
|
'status',
|
|
237
234
|
'priority',
|
|
@@ -501,6 +498,7 @@ class GatewayToolExecutor {
|
|
|
501
498
|
telegramGateway = null;
|
|
502
499
|
roleManager;
|
|
503
500
|
privateConnectorPolicy;
|
|
501
|
+
connectorEventAdapter;
|
|
504
502
|
executionContextStorage = new async_hooks_1.AsyncLocalStorage();
|
|
505
503
|
envelopeEnforcer = new index_js_1.EnvelopeEnforcer();
|
|
506
504
|
channelGrantProvider;
|
|
@@ -539,6 +537,95 @@ class GatewayToolExecutor {
|
|
|
539
537
|
}
|
|
540
538
|
taskLedger = null;
|
|
541
539
|
ownerEventEffectLedger = null;
|
|
540
|
+
ownerActionEffectLedger = null;
|
|
541
|
+
createNativeEffectObserver(state) {
|
|
542
|
+
const ledger = this.ownerActionEffectLedger;
|
|
543
|
+
if (!ledger || !state?.envelope || !state.modelRunId || state.memberScopeRequired) {
|
|
544
|
+
return undefined;
|
|
545
|
+
}
|
|
546
|
+
const attempt = state.workorderAttemptId === undefined
|
|
547
|
+
? null
|
|
548
|
+
: this.taskLedger?.getWorkOrderById(state.workorderAttemptId);
|
|
549
|
+
const occurrenceKey = attempt ? `workorder:${attempt.idempotencyKey}` : state.sourceMessageRef;
|
|
550
|
+
if (!occurrenceKey) {
|
|
551
|
+
throw new Error('Native effect observation needs an owner occurrence');
|
|
552
|
+
}
|
|
553
|
+
const context = {
|
|
554
|
+
ownerScope: state.agentContext?.principalId ?? 'owner:runtime',
|
|
555
|
+
occurrenceKey,
|
|
556
|
+
modelRunId: state.modelRunId,
|
|
557
|
+
envelopeHash: state.envelope.envelope_hash,
|
|
558
|
+
...(attempt ? { workOrderAttemptId: attempt.id } : {}),
|
|
559
|
+
};
|
|
560
|
+
if (ledger.hasUnsafeReplayEffects(occurrenceKey)) {
|
|
561
|
+
throw new types_js_1.AgentError('Owner occurrence needs effect reconciliation before replay', 'CODE_ACT_MUTATION_OUTCOME_UNKNOWN', undefined, false);
|
|
562
|
+
}
|
|
563
|
+
const admissionKey = `native-run:${(0, crypto_1.createHash)('sha256').update(context.modelRunId).digest('hex')}`;
|
|
564
|
+
ledger.begin(context, admissionKey, 'native_run', { admitted: true });
|
|
565
|
+
let sequence = 0;
|
|
566
|
+
const pending = new Map();
|
|
567
|
+
return {
|
|
568
|
+
started: (name, input) => {
|
|
569
|
+
const id = typeof input.nativeToolUseId === 'string'
|
|
570
|
+
? input.nativeToolUseId
|
|
571
|
+
: `observed:${++sequence}`;
|
|
572
|
+
const key = `native:${(0, crypto_1.createHash)('sha256').update(`${context.modelRunId}:${id}`).digest('hex')}`;
|
|
573
|
+
const reservation = ledger.begin(context, key, 'native_tool', { toolName: name });
|
|
574
|
+
if (reservation.state !== 'execute') {
|
|
575
|
+
throw new types_js_1.AgentError('Native effect already observed', 'CODE_ACT_MUTATION_OUTCOME_UNKNOWN', undefined, false);
|
|
576
|
+
}
|
|
577
|
+
pending.set(id, { key, name });
|
|
578
|
+
},
|
|
579
|
+
settled: (name, id, isError) => {
|
|
580
|
+
const match = pending.get(id) ?? [...pending.values()].find((entry) => entry.name === name);
|
|
581
|
+
if (!match) {
|
|
582
|
+
// Completion may be the first provider observation after reconnect.
|
|
583
|
+
const key = `native:${(0, crypto_1.createHash)('sha256').update(`${context.modelRunId}:${id}`).digest('hex')}`;
|
|
584
|
+
ledger.begin(context, key, 'native_tool', { toolName: name });
|
|
585
|
+
ledger.markUnknown(context, key, 'native_tool', 'completion observed without matching start');
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (isError) {
|
|
589
|
+
ledger.markUnknown(context, match.key, 'native_tool', 'native tool reported failure');
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
ledger.confirm(context, match.key, 'native_tool', { success: true });
|
|
593
|
+
}
|
|
594
|
+
for (const [pendingId, entry] of pending) {
|
|
595
|
+
if (entry === match) {
|
|
596
|
+
pending.delete(pendingId);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
finished: () => {
|
|
601
|
+
if (pending.size > 0) {
|
|
602
|
+
throw new types_js_1.AgentError('Native effects did not settle before final response', 'CODE_ACT_MUTATION_OUTCOME_UNKNOWN', undefined, false);
|
|
603
|
+
}
|
|
604
|
+
ledger.confirm(context, admissionKey, 'native_run', { completed: true });
|
|
605
|
+
},
|
|
606
|
+
interrupted: () => {
|
|
607
|
+
ledger.markUnknown(context, admissionKey, 'native_run', 'native turn did not finish cleanly');
|
|
608
|
+
for (const { key } of pending.values()) {
|
|
609
|
+
ledger.markUnknown(context, key, 'native_tool', 'native run interrupted');
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
setOwnerActionEffectLedger(ledger) {
|
|
615
|
+
this.ownerActionEffectLedger = ledger;
|
|
616
|
+
}
|
|
617
|
+
ownerEffectPort(context) {
|
|
618
|
+
const ledger = this.ownerActionEffectLedger;
|
|
619
|
+
if (!ledger) {
|
|
620
|
+
throw new Error('Owner action effect ledger is not configured');
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
begin: (_batch, key, kind, intent) => ledger.begin(context, key, kind, intent),
|
|
624
|
+
inspect: (_batch, key, kind) => ledger.inspect(context, key, kind),
|
|
625
|
+
confirm: (_batch, key, kind, result) => ledger.confirm(context, key, kind, result),
|
|
626
|
+
markUnknown: (_batch, key, kind, error) => ledger.markUnknown(context, key, kind, error),
|
|
627
|
+
};
|
|
628
|
+
}
|
|
542
629
|
setTaskLedger(ledger) {
|
|
543
630
|
this.taskLedger = ledger;
|
|
544
631
|
}
|
|
@@ -765,7 +852,10 @@ class GatewayToolExecutor {
|
|
|
765
852
|
constructor(options = {}) {
|
|
766
853
|
this.channelGrantProvider = options.channelGrantProvider ?? read_js_1.liveBoundaryChannels;
|
|
767
854
|
const privateWorkspaceRoot = (0, path_1.resolve)(process.env.MAMA_WORKSPACE || (0, path_1.join)((0, os_1.homedir)(), '.mama', 'workspace'));
|
|
768
|
-
this.driveTools = new drive_tools_js_1.DriveToolService({
|
|
855
|
+
this.driveTools = new drive_tools_js_1.DriveToolService({
|
|
856
|
+
workspaceRoot: privateWorkspaceRoot,
|
|
857
|
+
runGws: options.driveGwsRunner,
|
|
858
|
+
});
|
|
769
859
|
this.imageTranslationTools = new image_translation_tools_js_1.ImageTranslationToolService({
|
|
770
860
|
workspaceRoot: privateWorkspaceRoot,
|
|
771
861
|
});
|
|
@@ -794,6 +884,7 @@ class GatewayToolExecutor {
|
|
|
794
884
|
this.roleManager = (0, role_manager_js_1.getRoleManager)(options.rolesConfig ? { rolesConfig: options.rolesConfig } : undefined);
|
|
795
885
|
this.privateConnectorPolicy =
|
|
796
886
|
options.privateConnectorPolicy ?? DEFAULT_PRIVATE_CONNECTOR_POLICY;
|
|
887
|
+
this.connectorEventAdapter = options.connectorEventAdapter;
|
|
797
888
|
if (options.mamaApi) {
|
|
798
889
|
this.mamaApi = options.mamaApi;
|
|
799
890
|
}
|
|
@@ -1336,6 +1427,75 @@ class GatewayToolExecutor {
|
|
|
1336
1427
|
}
|
|
1337
1428
|
return modelRunId;
|
|
1338
1429
|
}
|
|
1430
|
+
async requireOwnerActionContext() {
|
|
1431
|
+
const state = this.getExecutionState();
|
|
1432
|
+
const envelope = state.envelope;
|
|
1433
|
+
if (!envelope || !state.modelRunId || state.memberScopeRequired) {
|
|
1434
|
+
throw new types_js_1.AgentError('Owner action requires the current authenticated owner envelope and model run', 'WORKORDER_SUPERSEDED', undefined, false);
|
|
1435
|
+
}
|
|
1436
|
+
const api = await this.initializeMAMAApi();
|
|
1437
|
+
const run = await api.getModelRun?.(state.modelRunId);
|
|
1438
|
+
if (!run || run.status !== 'running' || run.envelope_hash !== envelope.envelope_hash) {
|
|
1439
|
+
throw new Error('Owner action model run is no longer current for this envelope');
|
|
1440
|
+
}
|
|
1441
|
+
const attempt = state.workorderAttemptId === undefined
|
|
1442
|
+
? null
|
|
1443
|
+
: this.taskLedger?.getWorkOrderById(state.workorderAttemptId);
|
|
1444
|
+
if (state.workorderAttemptId !== undefined &&
|
|
1445
|
+
(!attempt ||
|
|
1446
|
+
attempt.status !== 'in_progress' ||
|
|
1447
|
+
run.input_refs?.workorderAttemptId !== attempt.id)) {
|
|
1448
|
+
throw new Error('Owner action workorder attempt is no longer active');
|
|
1449
|
+
}
|
|
1450
|
+
const occurrenceKey = attempt ? `workorder:${attempt.idempotencyKey}` : state.sourceMessageRef;
|
|
1451
|
+
if (!occurrenceKey) {
|
|
1452
|
+
throw new Error('Owner action is missing its host-issued occurrence identity');
|
|
1453
|
+
}
|
|
1454
|
+
return {
|
|
1455
|
+
ownerScope: state.agentContext?.principalId ?? 'owner:runtime',
|
|
1456
|
+
occurrenceKey,
|
|
1457
|
+
modelRunId: state.modelRunId,
|
|
1458
|
+
envelopeHash: envelope.envelope_hash,
|
|
1459
|
+
...(attempt ? { workOrderAttemptId: attempt.id } : {}),
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
temporalCorroborationVisible = (ref) => {
|
|
1463
|
+
if (typeof ref.connector !== 'string' || typeof ref.channel_id !== 'string') {
|
|
1464
|
+
return false;
|
|
1465
|
+
}
|
|
1466
|
+
return this.currentOwnerPartitionVisibility()({
|
|
1467
|
+
connector: ref.connector,
|
|
1468
|
+
channel: ref.channel_id,
|
|
1469
|
+
});
|
|
1470
|
+
};
|
|
1471
|
+
currentOwnerPartitionVisibility() {
|
|
1472
|
+
const state = this.getExecutionState();
|
|
1473
|
+
if (!state.envelope || state.memberScopeRequired) {
|
|
1474
|
+
throw new Error('Owner source visibility requires an owner envelope');
|
|
1475
|
+
}
|
|
1476
|
+
const channels = (0, read_js_1.narrowGrantToEnvelope)(snapshotChannelGrant(this.channelGrantProvider), {
|
|
1477
|
+
connectors: state.envelope.scope.raw_connectors,
|
|
1478
|
+
scopes: state.envelope.scope.memory_scopes,
|
|
1479
|
+
});
|
|
1480
|
+
return ({ connector, channel }) => channels[connector]?.includes(channel) === true;
|
|
1481
|
+
}
|
|
1482
|
+
async externalCandidateSource(candidateId) {
|
|
1483
|
+
const attemptId = this.getExecutionState().workorderAttemptId;
|
|
1484
|
+
if (attemptId !== undefined &&
|
|
1485
|
+
this.taskLedger?.getWorkOrderById(attemptId)?.workKind === 'board') {
|
|
1486
|
+
await this.requireExternalLifecycleModelRun(attemptId);
|
|
1487
|
+
if (this.getExecutionState().envelope) {
|
|
1488
|
+
const context = await this.requireOwnerActionContext();
|
|
1489
|
+
if (this.taskLedger
|
|
1490
|
+
.listOwnerActionCandidates(context)
|
|
1491
|
+
.some((candidate) => candidate.candidateId === candidateId)) {
|
|
1492
|
+
return { kind: 'owner_run', context };
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
return { kind: 'board', attemptId };
|
|
1496
|
+
}
|
|
1497
|
+
return { kind: 'owner_run', context: await this.requireOwnerActionContext() };
|
|
1498
|
+
}
|
|
1339
1499
|
async beginTraceIfNeeded(ctx, gatewayCallId) {
|
|
1340
1500
|
if (ctx.modelRunId) {
|
|
1341
1501
|
const api = await this.initializeMAMAApi();
|
|
@@ -1878,9 +2038,9 @@ class GatewayToolExecutor {
|
|
|
1878
2038
|
case 'Bash':
|
|
1879
2039
|
return await this.executeBash(input);
|
|
1880
2040
|
case 'discord_send':
|
|
1881
|
-
return await this.executeDiscordSend(input);
|
|
2041
|
+
return await this.executeOwnerWorkspaceEffect('discord_send', input, () => this.executeDiscordSend(input));
|
|
1882
2042
|
case 'slack_send':
|
|
1883
|
-
return await this.executeSlackSend(input);
|
|
2043
|
+
return await this.executeOwnerWorkspaceEffect('slack_send', input, () => this.executeSlackSend(input));
|
|
1884
2044
|
case 'telegram_send':
|
|
1885
2045
|
return await this.executeTelegramSend(input);
|
|
1886
2046
|
case 'ocr_image': {
|
|
@@ -2106,16 +2266,35 @@ class GatewayToolExecutor {
|
|
|
2106
2266
|
const uploadInput = input;
|
|
2107
2267
|
const state = this.getExecutionState();
|
|
2108
2268
|
const sourceMessageRef = state.sourceMessageRef;
|
|
2109
|
-
|
|
2269
|
+
const legacyOwnerEvent = sourceMessageRef?.startsWith('owner-event:') === true;
|
|
2270
|
+
if (!legacyOwnerEvent && !this.ownerActionEffectLedger) {
|
|
2110
2271
|
return {
|
|
2111
2272
|
success: true,
|
|
2112
2273
|
result: (0, drive_tools_js_1.asUntrustedDriveEvidence)(await this.driveTools.upload(uploadInput)),
|
|
2113
2274
|
};
|
|
2114
2275
|
}
|
|
2115
|
-
const
|
|
2116
|
-
|
|
2117
|
-
|
|
2276
|
+
const requestSha256 = (0, crypto_1.createHash)('sha256')
|
|
2277
|
+
.update(JSON.stringify([
|
|
2278
|
+
uploadInput.localPath,
|
|
2279
|
+
uploadInput.folderId,
|
|
2280
|
+
uploadInput.fileName ?? null,
|
|
2281
|
+
uploadInput.destinationCapability ?? null,
|
|
2282
|
+
]))
|
|
2283
|
+
.digest('hex');
|
|
2284
|
+
const context = legacyOwnerEvent ? null : await this.requireOwnerActionContext();
|
|
2285
|
+
const effect = legacyOwnerEvent
|
|
2286
|
+
? this.requireOwnerEventEffect(uploadInput.effect_key, 'drive_upload')
|
|
2287
|
+
: { batchId: 0, actionKey: `drive:${uploadInput.effect_key ?? requestSha256}` };
|
|
2288
|
+
const ledger = context
|
|
2289
|
+
? this.ownerEffectPort(context)
|
|
2290
|
+
: this.requireOwnerEventEffectLedger();
|
|
2291
|
+
const occurrence = context
|
|
2292
|
+
? `${context.ownerScope}:${context.occurrenceKey}:drive:${effect.actionKey}`
|
|
2293
|
+
: `${sourceMessageRef}:drive:${effect.actionKey}`;
|
|
2118
2294
|
let reservation = ledger.inspect(effect.batchId, effect.actionKey, 'drive_upload');
|
|
2295
|
+
if (context && reservation && reservation.intent.requestSha256 !== requestSha256) {
|
|
2296
|
+
throw new Error('Drive action key was already reserved for a different upload request');
|
|
2297
|
+
}
|
|
2119
2298
|
if (reservation?.state === 'confirmed') {
|
|
2120
2299
|
if (!reservation.result) {
|
|
2121
2300
|
throw new Error('Confirmed Drive upload is missing its durable result');
|
|
@@ -2129,6 +2308,7 @@ class GatewayToolExecutor {
|
|
|
2129
2308
|
reservation = ledger.begin(effect.batchId, effect.actionKey, 'drive_upload', {
|
|
2130
2309
|
folderId: preparation.prepared.folderId,
|
|
2131
2310
|
fileName: preparation.prepared.fileName,
|
|
2311
|
+
...(context ? { requestSha256 } : {}),
|
|
2132
2312
|
});
|
|
2133
2313
|
if (reservation.state === 'confirmed') {
|
|
2134
2314
|
if (!reservation.result) {
|
|
@@ -2173,8 +2353,7 @@ class GatewayToolExecutor {
|
|
|
2173
2353
|
case 'os_get_config':
|
|
2174
2354
|
return await this.executeGetConfig(input);
|
|
2175
2355
|
case 'webchat_send':
|
|
2176
|
-
return await this.
|
|
2177
|
-
);
|
|
2356
|
+
return await this.executeOwnerWorkspaceEffect('webchat_send', input, () => this.executeWebchatSend(input));
|
|
2178
2357
|
// Code-Act sandbox execution
|
|
2179
2358
|
case 'code_act':
|
|
2180
2359
|
return await this.executeCodeAct(input);
|
|
@@ -2296,16 +2475,20 @@ class GatewayToolExecutor {
|
|
|
2296
2475
|
if (!slotsInput || typeof slotsInput !== 'object') {
|
|
2297
2476
|
throw new types_js_1.AgentError('report_publish requires slots object', 'TOOL_ERROR', undefined, false);
|
|
2298
2477
|
}
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2478
|
+
const basisRevision = input.basis_revision;
|
|
2479
|
+
if (basisRevision !== undefined &&
|
|
2480
|
+
basisRevision !== null &&
|
|
2481
|
+
(typeof basisRevision !== 'string' ||
|
|
2482
|
+
!basisRevision.trim() ||
|
|
2483
|
+
basisRevision !== basisRevision.trim())) {
|
|
2484
|
+
throw new types_js_1.AgentError('report_publish basis_revision must be a canonical task basis from board_read', 'TOOL_ERROR', undefined, false);
|
|
2306
2485
|
}
|
|
2307
2486
|
if (this.reportPublisher) {
|
|
2308
|
-
const publication =
|
|
2487
|
+
const publication = basisRevision === undefined
|
|
2488
|
+
? this.reportPublisher(slotsInput)
|
|
2489
|
+
: this.reportPublisher(slotsInput, {
|
|
2490
|
+
basisRevision: basisRevision,
|
|
2491
|
+
});
|
|
2309
2492
|
// Backward compatibility: older injected publishers return void
|
|
2310
2493
|
// or the exact changed slot array. Production distinguishes slots
|
|
2311
2494
|
// accepted as present from slots whose HTML actually changed.
|
|
@@ -2632,13 +2815,23 @@ class GatewayToolExecutor {
|
|
|
2632
2815
|
}
|
|
2633
2816
|
case 'wiki_read': {
|
|
2634
2817
|
const wikiAuthority = this.getExecutionState().wikiTaskRange;
|
|
2635
|
-
if (!wikiAuthority || wikiAuthority.ownerDate === null) {
|
|
2636
|
-
throw new types_js_1.AgentError('wiki_read requires a host-issued wiki workorder ownerDate', 'WORKORDER_SUPERSEDED', undefined, false);
|
|
2637
|
-
}
|
|
2638
2818
|
if (!this.obsidianVaultPath) {
|
|
2639
2819
|
throw new types_js_1.AgentError('Wiki vault path not configured', 'TOOL_ERROR', undefined, false);
|
|
2640
2820
|
}
|
|
2641
2821
|
try {
|
|
2822
|
+
if (!wikiAuthority) {
|
|
2823
|
+
const readInput = input;
|
|
2824
|
+
return {
|
|
2825
|
+
success: true,
|
|
2826
|
+
...(0, wiki_read_js_1.readWikiPages)({
|
|
2827
|
+
root: this.obsidianVaultPath,
|
|
2828
|
+
paths: readInput.paths,
|
|
2829
|
+
contentOffset: readInput.content_offset,
|
|
2830
|
+
contentLimit: readInput.content_limit,
|
|
2831
|
+
contentVersions: readInput.content_versions,
|
|
2832
|
+
}),
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2642
2835
|
const attemptId = this.getExecutionState().workorderAttemptId;
|
|
2643
2836
|
if (!Number.isSafeInteger(attemptId) || attemptId < 1) {
|
|
2644
2837
|
throw new Error('wiki_read requires a host-issued workorder attempt');
|
|
@@ -2650,7 +2843,7 @@ class GatewayToolExecutor {
|
|
|
2650
2843
|
}
|
|
2651
2844
|
const paths = Array.isArray(readInput.paths) ? readInput.paths : [];
|
|
2652
2845
|
for (const rawPath of paths) {
|
|
2653
|
-
const path = (0, wiki_read_js_1.
|
|
2846
|
+
const path = (0, wiki_read_js_1.normalizeWikiRelativePath)(rawPath);
|
|
2654
2847
|
const key = this.wikiReadCoverageKey(attemptId, path);
|
|
2655
2848
|
const prior = this.wikiReadCoverage.get(key);
|
|
2656
2849
|
if (requestedOffset === 0) {
|
|
@@ -2662,10 +2855,17 @@ class GatewayToolExecutor {
|
|
|
2662
2855
|
}
|
|
2663
2856
|
const result = (0, wiki_read_js_1.readWikiPages)({
|
|
2664
2857
|
root: this.obsidianVaultPath,
|
|
2665
|
-
ownerDate: wikiAuthority.ownerDate,
|
|
2666
2858
|
paths: readInput.paths,
|
|
2667
2859
|
contentOffset: readInput.content_offset,
|
|
2668
2860
|
contentLimit: readInput.content_limit,
|
|
2861
|
+
contentVersions: Object.fromEntries(paths.map((rawPath) => {
|
|
2862
|
+
const path = (0, wiki_read_js_1.normalizeWikiRelativePath)(rawPath);
|
|
2863
|
+
return [
|
|
2864
|
+
path,
|
|
2865
|
+
this.wikiReadCoverage.get(this.wikiReadCoverageKey(attemptId, path))
|
|
2866
|
+
?.contentVersion ?? null,
|
|
2867
|
+
];
|
|
2868
|
+
})),
|
|
2669
2869
|
});
|
|
2670
2870
|
for (const page of result.pages) {
|
|
2671
2871
|
const key = this.wikiReadCoverageKey(attemptId, page.path);
|
|
@@ -2941,32 +3141,62 @@ class GatewayToolExecutor {
|
|
|
2941
3141
|
};
|
|
2942
3142
|
}
|
|
2943
3143
|
}
|
|
3144
|
+
case 'task_external_candidates': {
|
|
3145
|
+
if (!this.taskLedger) {
|
|
3146
|
+
throw new Error('Task ledger not configured');
|
|
3147
|
+
}
|
|
3148
|
+
const raw = input;
|
|
3149
|
+
if (!Array.isArray(raw.event_ids) ||
|
|
3150
|
+
raw.event_ids.length < 1 ||
|
|
3151
|
+
raw.event_ids.length > 20 ||
|
|
3152
|
+
raw.event_ids.some((id) => typeof id !== 'string' || !id.trim() || id !== id.trim())) {
|
|
3153
|
+
throw new Error('task_external_candidates requires 1-20 observed event_ids');
|
|
3154
|
+
}
|
|
3155
|
+
const context = await this.requireOwnerActionContext();
|
|
3156
|
+
const adapter = this.connectorEventAdapter ?? (await getContextPacketLookupAdapter());
|
|
3157
|
+
const result = (0, external_lifecycle_discovery_js_1.attestOwnerExternalLifecycleCandidates)({
|
|
3158
|
+
eventIds: raw.event_ids,
|
|
3159
|
+
context,
|
|
3160
|
+
ledger: this.taskLedger,
|
|
3161
|
+
getAdapter: () => adapter,
|
|
3162
|
+
privateConnectorPolicy: this.privateConnectorPolicy,
|
|
3163
|
+
rawConnectorScope: this.getExecutionState().envelope.scope.raw_connectors,
|
|
3164
|
+
isPartitionVisible: this.currentOwnerPartitionVisibility(),
|
|
3165
|
+
});
|
|
3166
|
+
return { success: true, ...result };
|
|
3167
|
+
}
|
|
2944
3168
|
case 'task_external_bind': {
|
|
2945
3169
|
if (!this.taskLedger) {
|
|
2946
3170
|
return { success: false, error: 'Task ledger not configured' };
|
|
2947
3171
|
}
|
|
2948
|
-
const state = this.getExecutionState();
|
|
2949
|
-
const attemptId = state.workorderAttemptId;
|
|
2950
|
-
if (attemptId === undefined || !Number.isSafeInteger(attemptId) || attemptId <= 0) {
|
|
2951
|
-
throw new types_js_1.AgentError('task_external_bind requires a trusted claimed board attempt', 'WORKORDER_SUPERSEDED', undefined, false);
|
|
2952
|
-
}
|
|
2953
3172
|
try {
|
|
2954
3173
|
(0, external_lifecycle_candidates_js_1.validateExternalLifecycleDecision)('binding', input);
|
|
2955
3174
|
}
|
|
2956
3175
|
catch (error) {
|
|
2957
3176
|
throw new types_js_1.AgentError(error instanceof Error ? error.message : 'task_external_bind input is invalid', 'TOOL_ERROR', error instanceof Error ? error : undefined, false);
|
|
2958
3177
|
}
|
|
2959
|
-
const
|
|
3178
|
+
const source = await this.externalCandidateSource(input.candidate_id);
|
|
3179
|
+
const attemptId = source.kind === 'board' ? source.attemptId : source.context.workOrderAttemptId;
|
|
3180
|
+
const modelRunId = source.kind === 'owner_run'
|
|
3181
|
+
? source.context.modelRunId
|
|
3182
|
+
: this.getExecutionState().modelRunId;
|
|
2960
3183
|
let candidate;
|
|
2961
3184
|
try {
|
|
2962
|
-
candidate = this.taskLedger.
|
|
3185
|
+
candidate = this.taskLedger.loadExternalCandidate(source, input.candidate_id, 'binding');
|
|
2963
3186
|
}
|
|
2964
3187
|
catch (error) {
|
|
2965
3188
|
throw new types_js_1.AgentError(error instanceof Error
|
|
2966
3189
|
? error.message
|
|
2967
3190
|
: 'task_external_bind candidate is unavailable', 'WORKORDER_SUPERSEDED', error instanceof Error ? error : undefined, false);
|
|
2968
3191
|
}
|
|
2969
|
-
|
|
3192
|
+
if (this.getExecutionState().envelope &&
|
|
3193
|
+
!this.currentOwnerPartitionVisibility()({
|
|
3194
|
+
connector: candidate.connector,
|
|
3195
|
+
channel: candidate.channelPartition,
|
|
3196
|
+
})) {
|
|
3197
|
+
throw new Error('External candidate source is no longer visible under the current owner grant');
|
|
3198
|
+
}
|
|
3199
|
+
const receipt = this.taskLedger.applyExternalBindingDecision(source, input, {
|
|
2970
3200
|
runId: modelRunId,
|
|
2971
3201
|
workOrderAttemptId: attemptId,
|
|
2972
3202
|
causeEventIds: [candidate.eventId],
|
|
@@ -2984,28 +3214,34 @@ class GatewayToolExecutor {
|
|
|
2984
3214
|
if (!this.taskLedger) {
|
|
2985
3215
|
return { success: false, error: 'Task ledger not configured' };
|
|
2986
3216
|
}
|
|
2987
|
-
const state = this.getExecutionState();
|
|
2988
|
-
const attemptId = state.workorderAttemptId;
|
|
2989
|
-
if (attemptId === undefined || !Number.isSafeInteger(attemptId) || attemptId <= 0) {
|
|
2990
|
-
throw new types_js_1.AgentError('task_lifecycle_reconcile requires a trusted claimed board attempt', 'WORKORDER_SUPERSEDED', undefined, false);
|
|
2991
|
-
}
|
|
2992
3217
|
try {
|
|
2993
3218
|
(0, external_lifecycle_candidates_js_1.validateExternalLifecycleDecision)('lifecycle', input);
|
|
2994
3219
|
}
|
|
2995
3220
|
catch (error) {
|
|
2996
3221
|
throw new types_js_1.AgentError(error instanceof Error ? error.message : 'task_lifecycle_reconcile input is invalid', 'TOOL_ERROR', error instanceof Error ? error : undefined, false);
|
|
2997
3222
|
}
|
|
2998
|
-
const
|
|
3223
|
+
const source = await this.externalCandidateSource(input.candidate_id);
|
|
3224
|
+
const attemptId = source.kind === 'board' ? source.attemptId : source.context.workOrderAttemptId;
|
|
3225
|
+
const modelRunId = source.kind === 'owner_run'
|
|
3226
|
+
? source.context.modelRunId
|
|
3227
|
+
: this.getExecutionState().modelRunId;
|
|
2999
3228
|
let candidate;
|
|
3000
3229
|
try {
|
|
3001
|
-
candidate = this.taskLedger.
|
|
3230
|
+
candidate = this.taskLedger.loadExternalCandidate(source, input.candidate_id, 'lifecycle');
|
|
3002
3231
|
}
|
|
3003
3232
|
catch (error) {
|
|
3004
3233
|
throw new types_js_1.AgentError(error instanceof Error
|
|
3005
3234
|
? error.message
|
|
3006
3235
|
: 'task_lifecycle_reconcile candidate is unavailable', 'WORKORDER_SUPERSEDED', error instanceof Error ? error : undefined, false);
|
|
3007
3236
|
}
|
|
3008
|
-
|
|
3237
|
+
if (this.getExecutionState().envelope &&
|
|
3238
|
+
!this.currentOwnerPartitionVisibility()({
|
|
3239
|
+
connector: candidate.connector,
|
|
3240
|
+
channel: candidate.channelPartition,
|
|
3241
|
+
})) {
|
|
3242
|
+
throw new Error('External candidate source is no longer visible under the current owner grant');
|
|
3243
|
+
}
|
|
3244
|
+
const receipt = this.taskLedger.applyExternalLifecycleDecision(source, input, {
|
|
3009
3245
|
runId: modelRunId,
|
|
3010
3246
|
workOrderAttemptId: attemptId,
|
|
3011
3247
|
causeEventIds: [candidate.eventId],
|
|
@@ -3041,20 +3277,29 @@ class GatewayToolExecutor {
|
|
|
3041
3277
|
if (completionCriteria.trim().length > task_ledger_js_1.TASK_COMPLETION_CRITERIA_MAX_LENGTH) {
|
|
3042
3278
|
throw new types_js_1.AgentError(`task_create completion_criteria must be at most ${task_ledger_js_1.TASK_COMPLETION_CRITERIA_MAX_LENGTH} characters`, 'TOOL_ERROR', undefined, false);
|
|
3043
3279
|
}
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
this.
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3280
|
+
const { creation_key: creationKey, ...taskInput } = rawTaskCreate;
|
|
3281
|
+
const create = () => this.taskLedger.create(taskInput, {
|
|
3282
|
+
runId: this.getExecutionState().modelRunId ?? null,
|
|
3283
|
+
workOrderAttemptId: this.getExecutionState().workorderAttemptId,
|
|
3284
|
+
requiresExpectedRevision: this.getExecutionState().workorderAttemptId !== undefined,
|
|
3285
|
+
causeEventIds: this.getExecutionState().causeEventIds,
|
|
3286
|
+
causeKind: this.getExecutionState().source === 'operator' ? 'clock' : 'owner_message',
|
|
3287
|
+
});
|
|
3288
|
+
if (this.ownerActionEffectLedger) {
|
|
3289
|
+
if (typeof creationKey !== 'string' ||
|
|
3290
|
+
!creationKey.trim() ||
|
|
3291
|
+
creationKey.length > 200) {
|
|
3292
|
+
throw new types_js_1.AgentError('task_create requires creation_key: a stable name for this logical task within the occurrence; reuse it on retry.', 'TOOL_ERROR', undefined, false);
|
|
3293
|
+
}
|
|
3294
|
+
const context = await this.requireOwnerActionContext();
|
|
3295
|
+
const receipt = this.ownerActionEffectLedger.atomic(context, `task:${creationKey}`, 'task_create', taskInput, () => ({ taskId: create().id }));
|
|
3296
|
+
const task = this.taskLedger.getById(Number(receipt.taskId));
|
|
3297
|
+
if (!task) {
|
|
3298
|
+
throw new Error('Created task receipt references a missing task');
|
|
3299
|
+
}
|
|
3300
|
+
return { success: true, task: (0, task_list_views_js_1.serializeTaskToolRecord)(task) };
|
|
3301
|
+
}
|
|
3302
|
+
return { success: true, task: (0, task_list_views_js_1.serializeTaskToolRecord)(create()) };
|
|
3058
3303
|
}
|
|
3059
3304
|
case 'task_reclassify': {
|
|
3060
3305
|
if (!this.taskLedger) {
|
|
@@ -3117,9 +3362,6 @@ class GatewayToolExecutor {
|
|
|
3117
3362
|
'Use latest_event for input; responses return latestEvent.', 'TOOL_ERROR', undefined, false);
|
|
3118
3363
|
}
|
|
3119
3364
|
if (Object.prototype.hasOwnProperty.call(rawTaskUpdate, 'completion_criteria')) {
|
|
3120
|
-
if (this.getExecutionState().source === 'owner-event') {
|
|
3121
|
-
throw new types_js_1.AgentError('task_update completion_criteria is unavailable on owner-event turns; full Board maintenance or the owner conversation owns qualification', 'TOOL_ERROR', undefined, false);
|
|
3122
|
-
}
|
|
3123
3365
|
const criteria = rawTaskUpdate.completion_criteria;
|
|
3124
3366
|
if (typeof criteria !== 'string' ||
|
|
3125
3367
|
criteria.trim().length === 0 ||
|
|
@@ -3155,23 +3397,47 @@ class GatewayToolExecutor {
|
|
|
3155
3397
|
}
|
|
3156
3398
|
const parsed = JSON.parse(packet.packet_json);
|
|
3157
3399
|
const selectedRaw = parsed.selected_evidence?.find((item) => item.ref?.kind === 'raw' && item.ref.raw_id === reviewAnchorRef)?.ref;
|
|
3400
|
+
if (!selectedRaw) {
|
|
3401
|
+
return {
|
|
3402
|
+
success: false,
|
|
3403
|
+
code: 'review_anchor_not_selected',
|
|
3404
|
+
error: 'The review anchor is not raw evidence selected in this context packet.',
|
|
3405
|
+
next_action: 'Read relevant raw evidence progressively and use a context packet that selects its actual raw_id. Do not guess an event ID or retry the same packet.',
|
|
3406
|
+
};
|
|
3407
|
+
}
|
|
3158
3408
|
const task = this.taskLedger.getById(id);
|
|
3159
3409
|
const connector = selectedRaw?.connector;
|
|
3160
3410
|
const channelId = selectedRaw?.channel_id;
|
|
3161
3411
|
const sourceChannel = typeof connector === 'string' && typeof channelId === 'string'
|
|
3162
3412
|
? `${connector}:${channelId}`
|
|
3163
3413
|
: null;
|
|
3164
|
-
if (!task || !sourceChannel
|
|
3165
|
-
throw new types_js_1.AgentError('task_update review anchor
|
|
3414
|
+
if (!task || !sourceChannel) {
|
|
3415
|
+
throw new types_js_1.AgentError('task_update review anchor has no verified source identity', 'TOOL_ERROR', undefined, false);
|
|
3416
|
+
}
|
|
3417
|
+
const visible = this.currentOwnerPartitionVisibility();
|
|
3418
|
+
const taskSourceSeparator = task.sourceChannel?.indexOf(':') ?? -1;
|
|
3419
|
+
if (!visible({ connector: connector, channel: channelId }) ||
|
|
3420
|
+
(task.sourceChannel &&
|
|
3421
|
+
(taskSourceSeparator < 1 ||
|
|
3422
|
+
!visible({
|
|
3423
|
+
connector: task.sourceChannel.slice(0, taskSourceSeparator),
|
|
3424
|
+
channel: task.sourceChannel.slice(taskSourceSeparator + 1),
|
|
3425
|
+
})))) {
|
|
3426
|
+
return {
|
|
3427
|
+
success: false,
|
|
3428
|
+
code: 'review_source_not_authorized',
|
|
3429
|
+
error: 'Current owner grants do not authorize the task source and review evidence source.',
|
|
3430
|
+
};
|
|
3166
3431
|
}
|
|
3167
|
-
const adapter = (
|
|
3432
|
+
const adapter = (this.connectorEventAdapter ??
|
|
3433
|
+
(await getContextPacketLookupAdapter()));
|
|
3168
3434
|
const anchor = adapter
|
|
3169
3435
|
.prepare(`SELECT source_connector, channel,
|
|
3170
3436
|
COALESCE(event_datetime, source_timestamp_ms) AS source_timestamp_ms
|
|
3171
3437
|
FROM connector_event_index
|
|
3172
3438
|
WHERE event_index_id = ?
|
|
3173
3439
|
LIMIT 1`)
|
|
3174
|
-
.
|
|
3440
|
+
.all(reviewAnchorRef)[0];
|
|
3175
3441
|
if (!anchor ||
|
|
3176
3442
|
anchor.source_connector !== connector ||
|
|
3177
3443
|
anchor.channel !== channelId ||
|
|
@@ -3264,8 +3530,8 @@ class GatewayToolExecutor {
|
|
|
3264
3530
|
(effectInput.outcome === 'deferred'
|
|
3265
3531
|
? Array.isArray(packet.source_refs) &&
|
|
3266
3532
|
packet.source_refs.length > 0 &&
|
|
3267
|
-
!temporalPacketReferencesBoundSource(context, packet.source_refs, reviewWindow)
|
|
3268
|
-
: !temporalPacketReferencesBoundSource(context, packet.source_refs, reviewWindow))) {
|
|
3533
|
+
!temporalPacketReferencesBoundSource(context, packet.source_refs, reviewWindow, this.temporalCorroborationVisible)
|
|
3534
|
+
: !temporalPacketReferencesBoundSource(context, packet.source_refs, reviewWindow, this.temporalCorroborationVisible))) {
|
|
3269
3535
|
throw new types_js_1.AgentError('task_temporal_reconcile context packet is not bound to the active task source', 'TOOL_ERROR', undefined, false);
|
|
3270
3536
|
}
|
|
3271
3537
|
const evidence = {
|
|
@@ -3393,6 +3659,61 @@ class GatewayToolExecutor {
|
|
|
3393
3659
|
return { success: false, error: 'Failed to read file: attachment extraction failed' };
|
|
3394
3660
|
}
|
|
3395
3661
|
}
|
|
3662
|
+
async executeOwnerWorkspaceEffect(kind, intent, execute) {
|
|
3663
|
+
const ledger = this.ownerActionEffectLedger;
|
|
3664
|
+
if (!ledger) {
|
|
3665
|
+
try {
|
|
3666
|
+
const { effectStarted: _notStarted, ...result } = await execute();
|
|
3667
|
+
return result;
|
|
3668
|
+
}
|
|
3669
|
+
catch (error) {
|
|
3670
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
const context = await this.requireOwnerActionContext();
|
|
3674
|
+
const digest = (0, crypto_1.createHash)('sha256').update((0, owner_action_effects_js_1.canonicalOwnerActionJson)(intent)).digest('hex');
|
|
3675
|
+
const actionKey = `${kind}:${digest}`;
|
|
3676
|
+
const reserved = ledger.begin(context, actionKey, kind, { inputSha256: digest });
|
|
3677
|
+
if (reserved.state === 'confirmed') {
|
|
3678
|
+
return {
|
|
3679
|
+
success: true,
|
|
3680
|
+
output: 'This exact action already completed in this occurrence; it was not executed again.',
|
|
3681
|
+
};
|
|
3682
|
+
}
|
|
3683
|
+
if (reserved.state !== 'execute') {
|
|
3684
|
+
throw new types_js_1.AgentError('Prior workspace effect needs reconciliation before replay', 'CODE_ACT_MUTATION_OUTCOME_UNKNOWN', undefined, false);
|
|
3685
|
+
}
|
|
3686
|
+
try {
|
|
3687
|
+
const result = await execute();
|
|
3688
|
+
if (!result.success && result.effectStarted === false) {
|
|
3689
|
+
ledger.releaseUnstarted(context, actionKey, kind);
|
|
3690
|
+
const { effectStarted: _notStarted, ...failure } = result;
|
|
3691
|
+
return failure;
|
|
3692
|
+
}
|
|
3693
|
+
if (!result.success) {
|
|
3694
|
+
throw new Error('Workspace effect returned failure; partial effects may exist');
|
|
3695
|
+
}
|
|
3696
|
+
ledger.confirm(context, actionKey, kind, { success: true });
|
|
3697
|
+
return result;
|
|
3698
|
+
}
|
|
3699
|
+
catch (error) {
|
|
3700
|
+
// A spawn failure proves the child never ran. A nonzero exit does NOT:
|
|
3701
|
+
// a command may write data before exiting with an error.
|
|
3702
|
+
const spawnError = error;
|
|
3703
|
+
if ((kind === 'Bash' || kind === 'obsidian') &&
|
|
3704
|
+
spawnError.code === 'ENOENT' &&
|
|
3705
|
+
typeof spawnError.syscall === 'string' &&
|
|
3706
|
+
spawnError.syscall.startsWith('spawn')) {
|
|
3707
|
+
ledger.releaseUnstarted(context, actionKey, kind);
|
|
3708
|
+
return {
|
|
3709
|
+
success: false,
|
|
3710
|
+
error: 'Executable or working directory is unavailable; no process started.',
|
|
3711
|
+
};
|
|
3712
|
+
}
|
|
3713
|
+
ledger.markUnknown(context, actionKey, kind, 'workspace effect did not settle successfully');
|
|
3714
|
+
throw new types_js_1.AgentError('Workspace effect outcome requires reconciliation', 'CODE_ACT_MUTATION_OUTCOME_UNKNOWN', error instanceof Error ? error : undefined, false);
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3396
3717
|
/**
|
|
3397
3718
|
* Execute Write tool - Write content to a file
|
|
3398
3719
|
* Checks path permissions based on current AgentContext
|
|
@@ -3422,15 +3743,12 @@ class GatewayToolExecutor {
|
|
|
3422
3743
|
return { success: false, error: `Access denied: Can only write files to ${mamaDir}` };
|
|
3423
3744
|
}
|
|
3424
3745
|
}
|
|
3425
|
-
|
|
3746
|
+
return this.executeOwnerWorkspaceEffect('Write', { path: (0, path_1.resolve)(expandedPath), content }, async () => {
|
|
3426
3747
|
const dir = (0, path_1.dirname)(expandedPath);
|
|
3427
3748
|
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
3428
3749
|
(0, fs_1.writeFileSync)(expandedPath, content, 'utf-8');
|
|
3429
3750
|
return { success: true };
|
|
3430
|
-
}
|
|
3431
|
-
catch (err) {
|
|
3432
|
-
return { success: false, error: `Failed to write file: ${err}` };
|
|
3433
|
-
}
|
|
3751
|
+
});
|
|
3434
3752
|
}
|
|
3435
3753
|
/**
|
|
3436
3754
|
* Execute Bash tool - Execute bash command
|
|
@@ -3545,36 +3863,25 @@ class GatewayToolExecutor {
|
|
|
3545
3863
|
};
|
|
3546
3864
|
}
|
|
3547
3865
|
}
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
success: true,
|
|
3558
|
-
|
|
3559
|
-
};
|
|
3560
|
-
}
|
|
3561
|
-
try {
|
|
3866
|
+
return this.executeOwnerWorkspaceEffect('Bash', { command, cwd: (0, path_1.resolve)(cwd) }, async () => {
|
|
3867
|
+
// A deferred restart is still a non-replayable shell effect.
|
|
3868
|
+
const restartPattern = /systemctl\s+--user\s+restart\s+mama-os/i;
|
|
3869
|
+
if (restartPattern.test(command)) {
|
|
3870
|
+
const child = (0, child_process_1.spawn)('bash', ['-c', 'sleep 3 && systemctl --user restart mama-os'], {
|
|
3871
|
+
detached: true,
|
|
3872
|
+
stdio: 'ignore',
|
|
3873
|
+
});
|
|
3874
|
+
child.unref();
|
|
3875
|
+
return { success: true, output: 'mama-os restart will execute in 3 seconds.' };
|
|
3876
|
+
}
|
|
3562
3877
|
const output = (0, child_process_1.execSync)(command, {
|
|
3563
|
-
cwd
|
|
3878
|
+
cwd,
|
|
3564
3879
|
encoding: 'utf-8',
|
|
3565
3880
|
maxBuffer: 10 * 1024 * 1024,
|
|
3566
3881
|
timeout: 60000,
|
|
3567
3882
|
});
|
|
3568
3883
|
return { success: true, output };
|
|
3569
|
-
|
|
3570
|
-
}
|
|
3571
|
-
catch (err) {
|
|
3572
|
-
return {
|
|
3573
|
-
success: false,
|
|
3574
|
-
error: `Command failed: ${err.message}`,
|
|
3575
|
-
output: err.stdout || err.stderr,
|
|
3576
|
-
};
|
|
3577
|
-
}
|
|
3884
|
+
});
|
|
3578
3885
|
}
|
|
3579
3886
|
/**
|
|
3580
3887
|
* Execute discord_send tool - Send message/file to Discord channel
|
|
@@ -3583,10 +3890,10 @@ class GatewayToolExecutor {
|
|
|
3583
3890
|
async executeDiscordSend(input) {
|
|
3584
3891
|
const { channel_id, message, image_path, file_path } = input;
|
|
3585
3892
|
if (!channel_id) {
|
|
3586
|
-
return { success: false, error: 'channel_id is required' };
|
|
3893
|
+
return { success: false, effectStarted: false, error: 'channel_id is required' };
|
|
3587
3894
|
}
|
|
3588
3895
|
if (!this.discordGateway) {
|
|
3589
|
-
return { success: false, error: 'Discord gateway not configured' };
|
|
3896
|
+
return { success: false, effectStarted: false, error: 'Discord gateway not configured' };
|
|
3590
3897
|
}
|
|
3591
3898
|
try {
|
|
3592
3899
|
// file_path takes precedence, fallback to image_path for backwards compatibility
|
|
@@ -3598,7 +3905,11 @@ class GatewayToolExecutor {
|
|
|
3598
3905
|
await this.discordGateway.sendMessage(channel_id, message);
|
|
3599
3906
|
}
|
|
3600
3907
|
else {
|
|
3601
|
-
return {
|
|
3908
|
+
return {
|
|
3909
|
+
success: false,
|
|
3910
|
+
effectStarted: false,
|
|
3911
|
+
error: 'Either message, file_path, or image_path is required',
|
|
3912
|
+
};
|
|
3602
3913
|
}
|
|
3603
3914
|
return { success: true };
|
|
3604
3915
|
}
|
|
@@ -3612,10 +3923,10 @@ class GatewayToolExecutor {
|
|
|
3612
3923
|
async executeSlackSend(input) {
|
|
3613
3924
|
const { channel_id, message, file_path } = input;
|
|
3614
3925
|
if (!channel_id) {
|
|
3615
|
-
return { success: false, error: 'channel_id is required' };
|
|
3926
|
+
return { success: false, effectStarted: false, error: 'channel_id is required' };
|
|
3616
3927
|
}
|
|
3617
3928
|
if (!this.slackGateway) {
|
|
3618
|
-
return { success: false, error: 'Slack gateway not configured' };
|
|
3929
|
+
return { success: false, effectStarted: false, error: 'Slack gateway not configured' };
|
|
3619
3930
|
}
|
|
3620
3931
|
try {
|
|
3621
3932
|
if (file_path) {
|
|
@@ -3625,7 +3936,11 @@ class GatewayToolExecutor {
|
|
|
3625
3936
|
await this.slackGateway.sendMessage(channel_id, message);
|
|
3626
3937
|
}
|
|
3627
3938
|
else {
|
|
3628
|
-
return {
|
|
3939
|
+
return {
|
|
3940
|
+
success: false,
|
|
3941
|
+
effectStarted: false,
|
|
3942
|
+
error: 'Either message or file_path is required',
|
|
3943
|
+
};
|
|
3629
3944
|
}
|
|
3630
3945
|
return { success: true };
|
|
3631
3946
|
}
|
|
@@ -3645,7 +3960,8 @@ class GatewayToolExecutor {
|
|
|
3645
3960
|
return { success: false, error: 'Telegram gateway not configured' };
|
|
3646
3961
|
}
|
|
3647
3962
|
const sourceMessageRef = this.getExecutionState().sourceMessageRef;
|
|
3648
|
-
const
|
|
3963
|
+
const legacyOwnerEvent = sourceMessageRef?.startsWith('owner-event:') === true;
|
|
3964
|
+
const ownerEvent = legacyOwnerEvent || this.ownerActionEffectLedger !== null;
|
|
3649
3965
|
let ownerEffect = null;
|
|
3650
3966
|
let ownerLedger = null;
|
|
3651
3967
|
let ownerIntent = null;
|
|
@@ -3655,9 +3971,27 @@ class GatewayToolExecutor {
|
|
|
3655
3971
|
// `-> error/missing receipt -> unknown -> reconcile-only
|
|
3656
3972
|
try {
|
|
3657
3973
|
if (ownerEvent) {
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3974
|
+
if (legacyOwnerEvent) {
|
|
3975
|
+
ownerEffect = this.requireOwnerEventEffect(delivery_key, 'telegram_send');
|
|
3976
|
+
ownerLedger = this.requireOwnerEventEffectLedger();
|
|
3977
|
+
idempotencyKey = `${sourceMessageRef}:telegram:${delivery_key}`;
|
|
3978
|
+
}
|
|
3979
|
+
else {
|
|
3980
|
+
const context = await this.requireOwnerActionContext();
|
|
3981
|
+
const identity = [
|
|
3982
|
+
sourceMessageRef ?? context.occurrenceKey,
|
|
3983
|
+
chat_id,
|
|
3984
|
+
message ?? null,
|
|
3985
|
+
file_path ?? null,
|
|
3986
|
+
sticker_emotion ?? null,
|
|
3987
|
+
];
|
|
3988
|
+
if (delivery_key !== undefined) {
|
|
3989
|
+
identity.push(delivery_key);
|
|
3990
|
+
}
|
|
3991
|
+
idempotencyKey = (0, crypto_1.createHash)('sha256').update(JSON.stringify(identity)).digest('hex');
|
|
3992
|
+
ownerEffect = { batchId: 0, actionKey: `telegram:${delivery_key ?? idempotencyKey}` };
|
|
3993
|
+
ownerLedger = this.ownerEffectPort(context);
|
|
3994
|
+
}
|
|
3661
3995
|
const existing = ownerLedger.inspect(ownerEffect.batchId, ownerEffect.actionKey, 'telegram_send');
|
|
3662
3996
|
if (existing && !(0, owner_event_effects_js_1.isOwnerEventTelegramIntentV1)(existing.intent)) {
|
|
3663
3997
|
return existing.state === 'confirmed'
|
|
@@ -3679,9 +4013,20 @@ class GatewayToolExecutor {
|
|
|
3679
4013
|
if (reservation.state === 'confirmed')
|
|
3680
4014
|
return { success: true };
|
|
3681
4015
|
if (reservation.state === 'reconcile') {
|
|
4016
|
+
const variants = ownerIntent.variant === 'image' ? ['image', 'file'] : [ownerIntent.variant];
|
|
4017
|
+
for (const variant of variants) {
|
|
4018
|
+
const receipt = this.telegramGateway.readOutboundDeliveryReceipt?.(idempotencyKey, variant);
|
|
4019
|
+
if (isExactTelegramDeliveryReceipt(receipt, idempotencyKey, variant)) {
|
|
4020
|
+
ownerLedger.confirm(ownerEffect.batchId, ownerEffect.actionKey, 'telegram_send', {
|
|
4021
|
+
version: 1,
|
|
4022
|
+
...receipt,
|
|
4023
|
+
});
|
|
4024
|
+
return { success: true };
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
3682
4027
|
return {
|
|
3683
4028
|
success: false,
|
|
3684
|
-
error: '
|
|
4029
|
+
error: 'Telegram delivery outcome is unconfirmed; reconcile the existing delivery before sending again.',
|
|
3685
4030
|
};
|
|
3686
4031
|
}
|
|
3687
4032
|
}
|
|
@@ -3895,11 +4240,14 @@ class GatewayToolExecutor {
|
|
|
3895
4240
|
async executeWebchatSend(input) {
|
|
3896
4241
|
const { message, file_path } = input;
|
|
3897
4242
|
if (!message && !file_path) {
|
|
3898
|
-
return {
|
|
4243
|
+
return {
|
|
4244
|
+
success: false,
|
|
4245
|
+
effectStarted: false,
|
|
4246
|
+
error: 'Either message or file_path is required',
|
|
4247
|
+
};
|
|
3899
4248
|
}
|
|
3900
4249
|
try {
|
|
3901
4250
|
const outboundDir = (0, path_1.join)((0, os_1.homedir)(), '.mama', 'workspace', 'media', 'outbound');
|
|
3902
|
-
(0, fs_1.mkdirSync)(outboundDir, { recursive: true });
|
|
3903
4251
|
if (file_path) {
|
|
3904
4252
|
// Expand ~ to home directory
|
|
3905
4253
|
const homeDir = (0, os_1.homedir)();
|
|
@@ -3909,7 +4257,7 @@ class GatewayToolExecutor {
|
|
|
3909
4257
|
// Check path permission based on role
|
|
3910
4258
|
const pathPermission = this.checkPathPermission(expandedPath);
|
|
3911
4259
|
if (!pathPermission.allowed) {
|
|
3912
|
-
return { success: false, error: pathPermission.error };
|
|
4260
|
+
return { success: false, effectStarted: false, error: pathPermission.error };
|
|
3913
4261
|
}
|
|
3914
4262
|
// Fallback security for contexts without path restrictions:
|
|
3915
4263
|
// Only allow reading from ~/.mama/ directory
|
|
@@ -3922,17 +4270,19 @@ class GatewayToolExecutor {
|
|
|
3922
4270
|
if (rel.startsWith('..') || (0, path_1.isAbsolute)(rel)) {
|
|
3923
4271
|
return {
|
|
3924
4272
|
success: false,
|
|
4273
|
+
effectStarted: false,
|
|
3925
4274
|
error: `Access denied: Can only copy files from ${mamaDir}`,
|
|
3926
4275
|
};
|
|
3927
4276
|
}
|
|
3928
4277
|
}
|
|
3929
4278
|
if (!(0, fs_1.existsSync)(expandedPath)) {
|
|
3930
|
-
return { success: false, error: `File not found: ${expandedPath}` };
|
|
4279
|
+
return { success: false, effectStarted: false, error: `File not found: ${expandedPath}` };
|
|
3931
4280
|
}
|
|
3932
4281
|
// Copy file to outbound directory with timestamp prefix
|
|
3933
4282
|
const baseName = (0, path_1.basename)(expandedPath) || 'file';
|
|
3934
4283
|
const outName = `${Date.now()}_${baseName}`;
|
|
3935
4284
|
const outPath = (0, path_1.join)(outboundDir, outName);
|
|
4285
|
+
(0, fs_1.mkdirSync)(outboundDir, { recursive: true });
|
|
3936
4286
|
(0, fs_1.copyFileSync)(expandedPath, outPath);
|
|
3937
4287
|
const viewerPath = `~/.mama/workspace/media/outbound/${outName}`;
|
|
3938
4288
|
return {
|
|
@@ -4063,28 +4413,45 @@ class GatewayToolExecutor {
|
|
|
4063
4413
|
catch (error) {
|
|
4064
4414
|
throw new types_js_1.AgentError(error instanceof Error ? error.message : 'Invalid Obsidian CLI arguments', 'TOOL_ERROR', undefined, false);
|
|
4065
4415
|
}
|
|
4066
|
-
|
|
4416
|
+
const invoke = async () => {
|
|
4067
4417
|
const { stdout } = await execFileAsync('obsidian', cliArgs, {
|
|
4068
4418
|
timeout: 15000,
|
|
4069
4419
|
encoding: 'utf-8',
|
|
4070
4420
|
maxBuffer: 1024 * 1024,
|
|
4071
4421
|
});
|
|
4072
|
-
return {
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4422
|
+
return { success: true, output: stdout.trim() };
|
|
4423
|
+
};
|
|
4424
|
+
const readCommands = new Set([
|
|
4425
|
+
'read',
|
|
4426
|
+
'daily:read',
|
|
4427
|
+
'search',
|
|
4428
|
+
'search:context',
|
|
4429
|
+
'files',
|
|
4430
|
+
'folders',
|
|
4431
|
+
'vault',
|
|
4432
|
+
'vaults',
|
|
4433
|
+
'links',
|
|
4434
|
+
'backlinks',
|
|
4435
|
+
'properties',
|
|
4436
|
+
'property:read',
|
|
4437
|
+
'aliases',
|
|
4438
|
+
'tags',
|
|
4439
|
+
]);
|
|
4440
|
+
try {
|
|
4441
|
+
const result = readCommands.has(command)
|
|
4442
|
+
? await invoke()
|
|
4443
|
+
: await this.executeOwnerWorkspaceEffect('obsidian', { command, args: args ?? {}, vault: this.obsidianVaultPath }, invoke);
|
|
4444
|
+
return result.success
|
|
4445
|
+
? { success: true, data: { output: result.output } }
|
|
4446
|
+
: result;
|
|
4076
4447
|
}
|
|
4077
|
-
catch (
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
return {
|
|
4081
|
-
success: false,
|
|
4082
|
-
error: 'Obsidian CLI unavailable (app not running). Use wiki_publish fallback.',
|
|
4083
|
-
};
|
|
4448
|
+
catch (error) {
|
|
4449
|
+
if (error instanceof types_js_1.AgentError) {
|
|
4450
|
+
throw error;
|
|
4084
4451
|
}
|
|
4085
4452
|
return {
|
|
4086
4453
|
success: false,
|
|
4087
|
-
error:
|
|
4454
|
+
error: 'Obsidian CLI unavailable or command failed; inspect the configured vault before retrying.',
|
|
4088
4455
|
};
|
|
4089
4456
|
}
|
|
4090
4457
|
}
|
|
@@ -4461,7 +4828,9 @@ class GatewayToolExecutor {
|
|
|
4461
4828
|
// prefix and causeEventIds: the agent never restates what the host knows.
|
|
4462
4829
|
if (temporalContext && this.taskLedger) {
|
|
4463
4830
|
const boundTask = this.taskLedger.getById(temporalContext.taskId);
|
|
4464
|
-
const rawChannel = boundTask?.
|
|
4831
|
+
const rawChannel = (boundTask?.status === 'review'
|
|
4832
|
+
? (boundTask.reviewAnchorSourceChannel ?? boundTask.sourceChannel)
|
|
4833
|
+
: boundTask?.sourceChannel) ?? null;
|
|
4465
4834
|
// Trimmed: a whitespace-only event id is truthy but fails ref
|
|
4466
4835
|
// normalization downstream - which would fail the WHOLE compile,
|
|
4467
4836
|
// exactly what "strictly additive" forbids.
|
|
@@ -4528,7 +4897,7 @@ class GatewayToolExecutor {
|
|
|
4528
4897
|
: undefined,
|
|
4529
4898
|
});
|
|
4530
4899
|
if (temporalContext &&
|
|
4531
|
-
!temporalPacketRawSourcesWithinBoundSource(temporalContext, result.packet.source_refs, reviewWindow)) {
|
|
4900
|
+
!temporalPacketRawSourcesWithinBoundSource(temporalContext, result.packet.source_refs, reviewWindow, this.temporalCorroborationVisible)) {
|
|
4532
4901
|
throw new Error('context_compile packet exceeds the active temporal task source');
|
|
4533
4902
|
}
|
|
4534
4903
|
if (wikiAuthority) {
|