@jungjaehoon/mama-os 0.49.3 → 0.51.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 +45 -10
- 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 +19 -10
- package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
- package/dist/agent/gateway-tool-executor.js +685 -180
- package/dist/agent/gateway-tool-executor.js.map +1 -1
- package/dist/agent/gateway-tools.md +8 -7
- 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 +17 -10
- 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/agent-raw-handler.d.ts +15 -1
- package/dist/api/agent-raw-handler.d.ts.map +1 -1
- package/dist/api/agent-raw-handler.js +14 -0
- package/dist/api/agent-raw-handler.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 +53 -162
- 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/concurrency/lane-manager.d.ts.map +1 -1
- package/dist/concurrency/lane-manager.js +3 -2
- package/dist/concurrency/lane-manager.js.map +1 -1
- package/dist/connectors/calendar/index.d.ts.map +1 -1
- package/dist/connectors/calendar/index.js +70 -37
- package/dist/connectors/calendar/index.js.map +1 -1
- package/dist/connectors/drive/index.d.ts.map +1 -1
- package/dist/connectors/drive/index.js +1 -0
- package/dist/connectors/drive/index.js.map +1 -1
- package/dist/connectors/framework/polling-scheduler.js +3 -3
- package/dist/connectors/framework/polling-scheduler.js.map +1 -1
- package/dist/connectors/framework/raw-store.d.ts +15 -1
- package/dist/connectors/framework/raw-store.d.ts.map +1 -1
- package/dist/connectors/framework/raw-store.js +128 -29
- package/dist/connectors/framework/raw-store.js.map +1 -1
- package/dist/connectors/framework/types.d.ts +9 -0
- package/dist/connectors/framework/types.d.ts.map +1 -1
- package/dist/connectors/trello/index.d.ts.map +1 -1
- package/dist/connectors/trello/index.js +1 -0
- package/dist/connectors/trello/index.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/db/migrations/raw-item-revisions.d.ts +4 -0
- package/dist/db/migrations/raw-item-revisions.d.ts.map +1 -0
- package/dist/db/migrations/raw-item-revisions.js +29 -0
- package/dist/db/migrations/raw-item-revisions.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-response-presenter.d.ts +4 -0
- package/dist/gateways/telegram-response-presenter.d.ts.map +1 -1
- package/dist/gateways/telegram-response-presenter.js +46 -41
- package/dist/gateways/telegram-response-presenter.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 +4 -0
- package/dist/gateways/telegram.d.ts.map +1 -1
- package/dist/gateways/telegram.js +52 -12
- 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/operator-trigger-loop.d.ts +4 -5
- package/dist/operator/operator-trigger-loop.d.ts.map +1 -1
- package/dist/operator/operator-trigger-loop.js +25 -44
- package/dist/operator/operator-trigger-loop.js.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/pending-report-store.d.ts +2 -2
- package/dist/operator/pending-report-store.d.ts.map +1 -1
- package/dist/operator/pending-report-store.js +9 -4
- package/dist/operator/pending-report-store.js.map +1 -1
- package/dist/operator/report-run.d.ts +10 -11
- package/dist/operator/report-run.d.ts.map +1 -1
- package/dist/operator/report-run.js +8 -48
- package/dist/operator/report-run.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 +2 -2
- package/public/viewer/operator/operator.js +3 -1
|
@@ -86,7 +86,10 @@ const obsidian_cli_args_js_1 = require("./obsidian-cli-args.js");
|
|
|
86
86
|
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
|
+
const temporal_reconcile_js_1 = require("../operator/temporal-reconcile.js");
|
|
89
90
|
const board_read_views_js_1 = require("../operator/board-read-views.js");
|
|
91
|
+
const owner_action_effects_js_1 = require("../operator/owner-action-effects.js");
|
|
92
|
+
const external_lifecycle_discovery_js_1 = require("../operator/external-lifecycle-discovery.js");
|
|
90
93
|
const read_js_1 = require("../evidence/read.js");
|
|
91
94
|
const private_connector_policy_js_1 = require("../connectors/private-connector-policy.js");
|
|
92
95
|
const member_candidate_store_js_1 = require("../gateways/member-candidate-store.js");
|
|
@@ -113,7 +116,7 @@ function bindTemporalContextPacketTask(context, task) {
|
|
|
113
116
|
function temporalIdentifierRef(value) {
|
|
114
117
|
return `sha256:${(0, crypto_1.createHash)('sha256').update(value).digest('hex')}`;
|
|
115
118
|
}
|
|
116
|
-
function temporalPacketRawSourcesWithinBoundSource(context, sourceRefs, reviewWindow) {
|
|
119
|
+
function temporalPacketRawSourcesWithinBoundSource(context, sourceRefs, reviewWindow, additionalVisible = () => false) {
|
|
117
120
|
const rawRefs = sourceRefs.filter((value) => typeof value === 'object' &&
|
|
118
121
|
value !== null &&
|
|
119
122
|
!Array.isArray(value) &&
|
|
@@ -121,29 +124,23 @@ function temporalPacketRawSourcesWithinBoundSource(context, sourceRefs, reviewWi
|
|
|
121
124
|
if (!context.sourceChannel) {
|
|
122
125
|
return rawRefs.length === 0;
|
|
123
126
|
}
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
127
|
+
const channelMatches = (ref) => typeof ref.connector === 'string' &&
|
|
128
|
+
typeof ref.channel_id === 'string' &&
|
|
129
|
+
temporalIdentifierRef(`${ref.connector}:${ref.channel_id}`) === context.sourceChannel;
|
|
130
|
+
const anchorMatches = (ref) => channelMatches(ref) &&
|
|
131
|
+
Boolean(context.sourceEventId &&
|
|
132
|
+
[ref.raw_id, ref.source_id].some((value) => typeof value === 'string' && temporalIdentifierRef(value) === context.sourceEventId));
|
|
133
|
+
return (rawRefs.every((ref) => (channelMatches(ref) &&
|
|
134
|
+
(reviewWindow !== undefined || !context.sourceEventId || anchorMatches(ref))) ||
|
|
135
|
+
additionalVisible(ref)) &&
|
|
136
|
+
((rawRefs.length === 0 && reviewWindow === undefined) ||
|
|
137
|
+
!context.sourceEventId ||
|
|
138
|
+
rawRefs.some(anchorMatches)));
|
|
136
139
|
}
|
|
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);
|
|
140
|
+
function temporalPacketReferencesBoundSource(context, sourceRefs, reviewWindow, additionalVisible) {
|
|
141
|
+
const hasRaw = sourceRefs.some((ref) => typeof ref === 'object' && ref !== null && ref.kind === 'raw');
|
|
142
|
+
return ((!(context.sourceEventId || context.sourceChannel) || hasRaw) &&
|
|
143
|
+
temporalPacketRawSourcesWithinBoundSource(context, sourceRefs, reviewWindow, additionalVisible));
|
|
147
144
|
}
|
|
148
145
|
function temporalPacketHasExactReviewWindow(packetJson, reviewWindow) {
|
|
149
146
|
try {
|
|
@@ -232,6 +229,7 @@ const TASK_UPDATE_PUBLIC_FIELD_SET = new Set(TASK_UPDATE_PUBLIC_FIELDS);
|
|
|
232
229
|
*/
|
|
233
230
|
const TASK_CREATE_PUBLIC_FIELDS = [
|
|
234
231
|
'title',
|
|
232
|
+
'creation_key',
|
|
235
233
|
'completion_criteria',
|
|
236
234
|
'status',
|
|
237
235
|
'priority',
|
|
@@ -501,6 +499,7 @@ class GatewayToolExecutor {
|
|
|
501
499
|
telegramGateway = null;
|
|
502
500
|
roleManager;
|
|
503
501
|
privateConnectorPolicy;
|
|
502
|
+
connectorEventAdapter;
|
|
504
503
|
executionContextStorage = new async_hooks_1.AsyncLocalStorage();
|
|
505
504
|
envelopeEnforcer = new index_js_1.EnvelopeEnforcer();
|
|
506
505
|
channelGrantProvider;
|
|
@@ -539,6 +538,95 @@ class GatewayToolExecutor {
|
|
|
539
538
|
}
|
|
540
539
|
taskLedger = null;
|
|
541
540
|
ownerEventEffectLedger = null;
|
|
541
|
+
ownerActionEffectLedger = null;
|
|
542
|
+
createNativeEffectObserver(state) {
|
|
543
|
+
const ledger = this.ownerActionEffectLedger;
|
|
544
|
+
if (!ledger || !state?.envelope || !state.modelRunId || state.memberScopeRequired) {
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
const attempt = state.workorderAttemptId === undefined
|
|
548
|
+
? null
|
|
549
|
+
: this.taskLedger?.getWorkOrderById(state.workorderAttemptId);
|
|
550
|
+
const occurrenceKey = attempt ? `workorder:${attempt.idempotencyKey}` : state.sourceMessageRef;
|
|
551
|
+
if (!occurrenceKey) {
|
|
552
|
+
throw new Error('Native effect observation needs an owner occurrence');
|
|
553
|
+
}
|
|
554
|
+
const context = {
|
|
555
|
+
ownerScope: state.agentContext?.principalId ?? 'owner:runtime',
|
|
556
|
+
occurrenceKey,
|
|
557
|
+
modelRunId: state.modelRunId,
|
|
558
|
+
envelopeHash: state.envelope.envelope_hash,
|
|
559
|
+
...(attempt ? { workOrderAttemptId: attempt.id } : {}),
|
|
560
|
+
};
|
|
561
|
+
if (ledger.hasUnsafeReplayEffects(occurrenceKey)) {
|
|
562
|
+
throw new types_js_1.AgentError('Owner occurrence needs effect reconciliation before replay', 'CODE_ACT_MUTATION_OUTCOME_UNKNOWN', undefined, false);
|
|
563
|
+
}
|
|
564
|
+
const admissionKey = `native-run:${(0, crypto_1.createHash)('sha256').update(context.modelRunId).digest('hex')}`;
|
|
565
|
+
ledger.begin(context, admissionKey, 'native_run', { admitted: true });
|
|
566
|
+
let sequence = 0;
|
|
567
|
+
const pending = new Map();
|
|
568
|
+
return {
|
|
569
|
+
started: (name, input) => {
|
|
570
|
+
const id = typeof input.nativeToolUseId === 'string'
|
|
571
|
+
? input.nativeToolUseId
|
|
572
|
+
: `observed:${++sequence}`;
|
|
573
|
+
const key = `native:${(0, crypto_1.createHash)('sha256').update(`${context.modelRunId}:${id}`).digest('hex')}`;
|
|
574
|
+
const reservation = ledger.begin(context, key, 'native_tool', { toolName: name });
|
|
575
|
+
if (reservation.state !== 'execute') {
|
|
576
|
+
throw new types_js_1.AgentError('Native effect already observed', 'CODE_ACT_MUTATION_OUTCOME_UNKNOWN', undefined, false);
|
|
577
|
+
}
|
|
578
|
+
pending.set(id, { key, name });
|
|
579
|
+
},
|
|
580
|
+
settled: (name, id, isError) => {
|
|
581
|
+
const match = pending.get(id) ?? [...pending.values()].find((entry) => entry.name === name);
|
|
582
|
+
if (!match) {
|
|
583
|
+
// Completion may be the first provider observation after reconnect.
|
|
584
|
+
const key = `native:${(0, crypto_1.createHash)('sha256').update(`${context.modelRunId}:${id}`).digest('hex')}`;
|
|
585
|
+
ledger.begin(context, key, 'native_tool', { toolName: name });
|
|
586
|
+
ledger.markUnknown(context, key, 'native_tool', 'completion observed without matching start');
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
if (isError) {
|
|
590
|
+
ledger.markUnknown(context, match.key, 'native_tool', 'native tool reported failure');
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
ledger.confirm(context, match.key, 'native_tool', { success: true });
|
|
594
|
+
}
|
|
595
|
+
for (const [pendingId, entry] of pending) {
|
|
596
|
+
if (entry === match) {
|
|
597
|
+
pending.delete(pendingId);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
finished: () => {
|
|
602
|
+
if (pending.size > 0) {
|
|
603
|
+
throw new types_js_1.AgentError('Native effects did not settle before final response', 'CODE_ACT_MUTATION_OUTCOME_UNKNOWN', undefined, false);
|
|
604
|
+
}
|
|
605
|
+
ledger.confirm(context, admissionKey, 'native_run', { completed: true });
|
|
606
|
+
},
|
|
607
|
+
interrupted: () => {
|
|
608
|
+
ledger.markUnknown(context, admissionKey, 'native_run', 'native turn did not finish cleanly');
|
|
609
|
+
for (const { key } of pending.values()) {
|
|
610
|
+
ledger.markUnknown(context, key, 'native_tool', 'native run interrupted');
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
setOwnerActionEffectLedger(ledger) {
|
|
616
|
+
this.ownerActionEffectLedger = ledger;
|
|
617
|
+
}
|
|
618
|
+
ownerEffectPort(context) {
|
|
619
|
+
const ledger = this.ownerActionEffectLedger;
|
|
620
|
+
if (!ledger) {
|
|
621
|
+
throw new Error('Owner action effect ledger is not configured');
|
|
622
|
+
}
|
|
623
|
+
return {
|
|
624
|
+
begin: (_batch, key, kind, intent) => ledger.begin(context, key, kind, intent),
|
|
625
|
+
inspect: (_batch, key, kind) => ledger.inspect(context, key, kind),
|
|
626
|
+
confirm: (_batch, key, kind, result) => ledger.confirm(context, key, kind, result),
|
|
627
|
+
markUnknown: (_batch, key, kind, error) => ledger.markUnknown(context, key, kind, error),
|
|
628
|
+
};
|
|
629
|
+
}
|
|
542
630
|
setTaskLedger(ledger) {
|
|
543
631
|
this.taskLedger = ledger;
|
|
544
632
|
}
|
|
@@ -765,7 +853,10 @@ class GatewayToolExecutor {
|
|
|
765
853
|
constructor(options = {}) {
|
|
766
854
|
this.channelGrantProvider = options.channelGrantProvider ?? read_js_1.liveBoundaryChannels;
|
|
767
855
|
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({
|
|
856
|
+
this.driveTools = new drive_tools_js_1.DriveToolService({
|
|
857
|
+
workspaceRoot: privateWorkspaceRoot,
|
|
858
|
+
runGws: options.driveGwsRunner,
|
|
859
|
+
});
|
|
769
860
|
this.imageTranslationTools = new image_translation_tools_js_1.ImageTranslationToolService({
|
|
770
861
|
workspaceRoot: privateWorkspaceRoot,
|
|
771
862
|
});
|
|
@@ -794,6 +885,7 @@ class GatewayToolExecutor {
|
|
|
794
885
|
this.roleManager = (0, role_manager_js_1.getRoleManager)(options.rolesConfig ? { rolesConfig: options.rolesConfig } : undefined);
|
|
795
886
|
this.privateConnectorPolicy =
|
|
796
887
|
options.privateConnectorPolicy ?? DEFAULT_PRIVATE_CONNECTOR_POLICY;
|
|
888
|
+
this.connectorEventAdapter = options.connectorEventAdapter;
|
|
797
889
|
if (options.mamaApi) {
|
|
798
890
|
this.mamaApi = options.mamaApi;
|
|
799
891
|
}
|
|
@@ -1336,6 +1428,75 @@ class GatewayToolExecutor {
|
|
|
1336
1428
|
}
|
|
1337
1429
|
return modelRunId;
|
|
1338
1430
|
}
|
|
1431
|
+
async requireOwnerActionContext() {
|
|
1432
|
+
const state = this.getExecutionState();
|
|
1433
|
+
const envelope = state.envelope;
|
|
1434
|
+
if (!envelope || !state.modelRunId || state.memberScopeRequired) {
|
|
1435
|
+
throw new types_js_1.AgentError('Owner action requires the current authenticated owner envelope and model run', 'WORKORDER_SUPERSEDED', undefined, false);
|
|
1436
|
+
}
|
|
1437
|
+
const api = await this.initializeMAMAApi();
|
|
1438
|
+
const run = await api.getModelRun?.(state.modelRunId);
|
|
1439
|
+
if (!run || run.status !== 'running' || run.envelope_hash !== envelope.envelope_hash) {
|
|
1440
|
+
throw new Error('Owner action model run is no longer current for this envelope');
|
|
1441
|
+
}
|
|
1442
|
+
const attempt = state.workorderAttemptId === undefined
|
|
1443
|
+
? null
|
|
1444
|
+
: this.taskLedger?.getWorkOrderById(state.workorderAttemptId);
|
|
1445
|
+
if (state.workorderAttemptId !== undefined &&
|
|
1446
|
+
(!attempt ||
|
|
1447
|
+
attempt.status !== 'in_progress' ||
|
|
1448
|
+
run.input_refs?.workorderAttemptId !== attempt.id)) {
|
|
1449
|
+
throw new Error('Owner action workorder attempt is no longer active');
|
|
1450
|
+
}
|
|
1451
|
+
const occurrenceKey = attempt ? `workorder:${attempt.idempotencyKey}` : state.sourceMessageRef;
|
|
1452
|
+
if (!occurrenceKey) {
|
|
1453
|
+
throw new Error('Owner action is missing its host-issued occurrence identity');
|
|
1454
|
+
}
|
|
1455
|
+
return {
|
|
1456
|
+
ownerScope: state.agentContext?.principalId ?? 'owner:runtime',
|
|
1457
|
+
occurrenceKey,
|
|
1458
|
+
modelRunId: state.modelRunId,
|
|
1459
|
+
envelopeHash: envelope.envelope_hash,
|
|
1460
|
+
...(attempt ? { workOrderAttemptId: attempt.id } : {}),
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
temporalCorroborationVisible = (ref) => {
|
|
1464
|
+
if (typeof ref.connector !== 'string' || typeof ref.channel_id !== 'string') {
|
|
1465
|
+
return false;
|
|
1466
|
+
}
|
|
1467
|
+
return this.currentOwnerPartitionVisibility()({
|
|
1468
|
+
connector: ref.connector,
|
|
1469
|
+
channel: ref.channel_id,
|
|
1470
|
+
});
|
|
1471
|
+
};
|
|
1472
|
+
currentOwnerPartitionVisibility() {
|
|
1473
|
+
const state = this.getExecutionState();
|
|
1474
|
+
if (!state.envelope || state.memberScopeRequired) {
|
|
1475
|
+
throw new Error('Owner source visibility requires an owner envelope');
|
|
1476
|
+
}
|
|
1477
|
+
const channels = (0, read_js_1.narrowGrantToEnvelope)(snapshotChannelGrant(this.channelGrantProvider), {
|
|
1478
|
+
connectors: state.envelope.scope.raw_connectors,
|
|
1479
|
+
scopes: state.envelope.scope.memory_scopes,
|
|
1480
|
+
});
|
|
1481
|
+
return ({ connector, channel }) => channels[connector]?.includes(channel) === true;
|
|
1482
|
+
}
|
|
1483
|
+
async externalCandidateSource(candidateId) {
|
|
1484
|
+
const attemptId = this.getExecutionState().workorderAttemptId;
|
|
1485
|
+
if (attemptId !== undefined &&
|
|
1486
|
+
this.taskLedger?.getWorkOrderById(attemptId)?.workKind === 'board') {
|
|
1487
|
+
await this.requireExternalLifecycleModelRun(attemptId);
|
|
1488
|
+
if (this.getExecutionState().envelope) {
|
|
1489
|
+
const context = await this.requireOwnerActionContext();
|
|
1490
|
+
if (this.taskLedger
|
|
1491
|
+
.listOwnerActionCandidates(context)
|
|
1492
|
+
.some((candidate) => candidate.candidateId === candidateId)) {
|
|
1493
|
+
return { kind: 'owner_run', context };
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
return { kind: 'board', attemptId };
|
|
1497
|
+
}
|
|
1498
|
+
return { kind: 'owner_run', context: await this.requireOwnerActionContext() };
|
|
1499
|
+
}
|
|
1339
1500
|
async beginTraceIfNeeded(ctx, gatewayCallId) {
|
|
1340
1501
|
if (ctx.modelRunId) {
|
|
1341
1502
|
const api = await this.initializeMAMAApi();
|
|
@@ -1878,9 +2039,9 @@ class GatewayToolExecutor {
|
|
|
1878
2039
|
case 'Bash':
|
|
1879
2040
|
return await this.executeBash(input);
|
|
1880
2041
|
case 'discord_send':
|
|
1881
|
-
return await this.executeDiscordSend(input);
|
|
2042
|
+
return await this.executeOwnerWorkspaceEffect('discord_send', input, () => this.executeDiscordSend(input));
|
|
1882
2043
|
case 'slack_send':
|
|
1883
|
-
return await this.executeSlackSend(input);
|
|
2044
|
+
return await this.executeOwnerWorkspaceEffect('slack_send', input, () => this.executeSlackSend(input));
|
|
1884
2045
|
case 'telegram_send':
|
|
1885
2046
|
return await this.executeTelegramSend(input);
|
|
1886
2047
|
case 'ocr_image': {
|
|
@@ -2106,16 +2267,35 @@ class GatewayToolExecutor {
|
|
|
2106
2267
|
const uploadInput = input;
|
|
2107
2268
|
const state = this.getExecutionState();
|
|
2108
2269
|
const sourceMessageRef = state.sourceMessageRef;
|
|
2109
|
-
|
|
2270
|
+
const legacyOwnerEvent = sourceMessageRef?.startsWith('owner-event:') === true;
|
|
2271
|
+
if (!legacyOwnerEvent && !this.ownerActionEffectLedger) {
|
|
2110
2272
|
return {
|
|
2111
2273
|
success: true,
|
|
2112
2274
|
result: (0, drive_tools_js_1.asUntrustedDriveEvidence)(await this.driveTools.upload(uploadInput)),
|
|
2113
2275
|
};
|
|
2114
2276
|
}
|
|
2115
|
-
const
|
|
2116
|
-
|
|
2117
|
-
|
|
2277
|
+
const requestSha256 = (0, crypto_1.createHash)('sha256')
|
|
2278
|
+
.update(JSON.stringify([
|
|
2279
|
+
uploadInput.localPath,
|
|
2280
|
+
uploadInput.folderId,
|
|
2281
|
+
uploadInput.fileName ?? null,
|
|
2282
|
+
uploadInput.destinationCapability ?? null,
|
|
2283
|
+
]))
|
|
2284
|
+
.digest('hex');
|
|
2285
|
+
const context = legacyOwnerEvent ? null : await this.requireOwnerActionContext();
|
|
2286
|
+
const effect = legacyOwnerEvent
|
|
2287
|
+
? this.requireOwnerEventEffect(uploadInput.effect_key, 'drive_upload')
|
|
2288
|
+
: { batchId: 0, actionKey: `drive:${uploadInput.effect_key ?? requestSha256}` };
|
|
2289
|
+
const ledger = context
|
|
2290
|
+
? this.ownerEffectPort(context)
|
|
2291
|
+
: this.requireOwnerEventEffectLedger();
|
|
2292
|
+
const occurrence = context
|
|
2293
|
+
? `${context.ownerScope}:${context.occurrenceKey}:drive:${effect.actionKey}`
|
|
2294
|
+
: `${sourceMessageRef}:drive:${effect.actionKey}`;
|
|
2118
2295
|
let reservation = ledger.inspect(effect.batchId, effect.actionKey, 'drive_upload');
|
|
2296
|
+
if (context && reservation && reservation.intent.requestSha256 !== requestSha256) {
|
|
2297
|
+
throw new Error('Drive action key was already reserved for a different upload request');
|
|
2298
|
+
}
|
|
2119
2299
|
if (reservation?.state === 'confirmed') {
|
|
2120
2300
|
if (!reservation.result) {
|
|
2121
2301
|
throw new Error('Confirmed Drive upload is missing its durable result');
|
|
@@ -2129,6 +2309,7 @@ class GatewayToolExecutor {
|
|
|
2129
2309
|
reservation = ledger.begin(effect.batchId, effect.actionKey, 'drive_upload', {
|
|
2130
2310
|
folderId: preparation.prepared.folderId,
|
|
2131
2311
|
fileName: preparation.prepared.fileName,
|
|
2312
|
+
...(context ? { requestSha256 } : {}),
|
|
2132
2313
|
});
|
|
2133
2314
|
if (reservation.state === 'confirmed') {
|
|
2134
2315
|
if (!reservation.result) {
|
|
@@ -2173,8 +2354,7 @@ class GatewayToolExecutor {
|
|
|
2173
2354
|
case 'os_get_config':
|
|
2174
2355
|
return await this.executeGetConfig(input);
|
|
2175
2356
|
case 'webchat_send':
|
|
2176
|
-
return await this.
|
|
2177
|
-
);
|
|
2357
|
+
return await this.executeOwnerWorkspaceEffect('webchat_send', input, () => this.executeWebchatSend(input));
|
|
2178
2358
|
// Code-Act sandbox execution
|
|
2179
2359
|
case 'code_act':
|
|
2180
2360
|
return await this.executeCodeAct(input);
|
|
@@ -2296,16 +2476,20 @@ class GatewayToolExecutor {
|
|
|
2296
2476
|
if (!slotsInput || typeof slotsInput !== 'object') {
|
|
2297
2477
|
throw new types_js_1.AgentError('report_publish requires slots object', 'TOOL_ERROR', undefined, false);
|
|
2298
2478
|
}
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2479
|
+
const basisRevision = input.basis_revision;
|
|
2480
|
+
if (basisRevision !== undefined &&
|
|
2481
|
+
basisRevision !== null &&
|
|
2482
|
+
(typeof basisRevision !== 'string' ||
|
|
2483
|
+
!basisRevision.trim() ||
|
|
2484
|
+
basisRevision !== basisRevision.trim())) {
|
|
2485
|
+
throw new types_js_1.AgentError('report_publish basis_revision must be a canonical task basis from board_read', 'TOOL_ERROR', undefined, false);
|
|
2306
2486
|
}
|
|
2307
2487
|
if (this.reportPublisher) {
|
|
2308
|
-
const publication =
|
|
2488
|
+
const publication = basisRevision === undefined
|
|
2489
|
+
? this.reportPublisher(slotsInput)
|
|
2490
|
+
: this.reportPublisher(slotsInput, {
|
|
2491
|
+
basisRevision: basisRevision,
|
|
2492
|
+
});
|
|
2309
2493
|
// Backward compatibility: older injected publishers return void
|
|
2310
2494
|
// or the exact changed slot array. Production distinguishes slots
|
|
2311
2495
|
// accepted as present from slots whose HTML actually changed.
|
|
@@ -2632,13 +2816,23 @@ class GatewayToolExecutor {
|
|
|
2632
2816
|
}
|
|
2633
2817
|
case 'wiki_read': {
|
|
2634
2818
|
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
2819
|
if (!this.obsidianVaultPath) {
|
|
2639
2820
|
throw new types_js_1.AgentError('Wiki vault path not configured', 'TOOL_ERROR', undefined, false);
|
|
2640
2821
|
}
|
|
2641
2822
|
try {
|
|
2823
|
+
if (!wikiAuthority) {
|
|
2824
|
+
const readInput = input;
|
|
2825
|
+
return {
|
|
2826
|
+
success: true,
|
|
2827
|
+
...(0, wiki_read_js_1.readWikiPages)({
|
|
2828
|
+
root: this.obsidianVaultPath,
|
|
2829
|
+
paths: readInput.paths,
|
|
2830
|
+
contentOffset: readInput.content_offset,
|
|
2831
|
+
contentLimit: readInput.content_limit,
|
|
2832
|
+
contentVersions: readInput.content_versions,
|
|
2833
|
+
}),
|
|
2834
|
+
};
|
|
2835
|
+
}
|
|
2642
2836
|
const attemptId = this.getExecutionState().workorderAttemptId;
|
|
2643
2837
|
if (!Number.isSafeInteger(attemptId) || attemptId < 1) {
|
|
2644
2838
|
throw new Error('wiki_read requires a host-issued workorder attempt');
|
|
@@ -2650,7 +2844,7 @@ class GatewayToolExecutor {
|
|
|
2650
2844
|
}
|
|
2651
2845
|
const paths = Array.isArray(readInput.paths) ? readInput.paths : [];
|
|
2652
2846
|
for (const rawPath of paths) {
|
|
2653
|
-
const path = (0, wiki_read_js_1.
|
|
2847
|
+
const path = (0, wiki_read_js_1.normalizeWikiRelativePath)(rawPath);
|
|
2654
2848
|
const key = this.wikiReadCoverageKey(attemptId, path);
|
|
2655
2849
|
const prior = this.wikiReadCoverage.get(key);
|
|
2656
2850
|
if (requestedOffset === 0) {
|
|
@@ -2662,10 +2856,17 @@ class GatewayToolExecutor {
|
|
|
2662
2856
|
}
|
|
2663
2857
|
const result = (0, wiki_read_js_1.readWikiPages)({
|
|
2664
2858
|
root: this.obsidianVaultPath,
|
|
2665
|
-
ownerDate: wikiAuthority.ownerDate,
|
|
2666
2859
|
paths: readInput.paths,
|
|
2667
2860
|
contentOffset: readInput.content_offset,
|
|
2668
2861
|
contentLimit: readInput.content_limit,
|
|
2862
|
+
contentVersions: Object.fromEntries(paths.map((rawPath) => {
|
|
2863
|
+
const path = (0, wiki_read_js_1.normalizeWikiRelativePath)(rawPath);
|
|
2864
|
+
return [
|
|
2865
|
+
path,
|
|
2866
|
+
this.wikiReadCoverage.get(this.wikiReadCoverageKey(attemptId, path))
|
|
2867
|
+
?.contentVersion ?? null,
|
|
2868
|
+
];
|
|
2869
|
+
})),
|
|
2669
2870
|
});
|
|
2670
2871
|
for (const page of result.pages) {
|
|
2671
2872
|
const key = this.wikiReadCoverageKey(attemptId, page.path);
|
|
@@ -2941,32 +3142,62 @@ class GatewayToolExecutor {
|
|
|
2941
3142
|
};
|
|
2942
3143
|
}
|
|
2943
3144
|
}
|
|
3145
|
+
case 'task_external_candidates': {
|
|
3146
|
+
if (!this.taskLedger) {
|
|
3147
|
+
throw new Error('Task ledger not configured');
|
|
3148
|
+
}
|
|
3149
|
+
const raw = input;
|
|
3150
|
+
if (!Array.isArray(raw.event_ids) ||
|
|
3151
|
+
raw.event_ids.length < 1 ||
|
|
3152
|
+
raw.event_ids.length > 20 ||
|
|
3153
|
+
raw.event_ids.some((id) => typeof id !== 'string' || !id.trim() || id !== id.trim())) {
|
|
3154
|
+
throw new Error('task_external_candidates requires 1-20 observed event_ids');
|
|
3155
|
+
}
|
|
3156
|
+
const context = await this.requireOwnerActionContext();
|
|
3157
|
+
const adapter = this.connectorEventAdapter ?? (await getContextPacketLookupAdapter());
|
|
3158
|
+
const result = (0, external_lifecycle_discovery_js_1.attestOwnerExternalLifecycleCandidates)({
|
|
3159
|
+
eventIds: raw.event_ids,
|
|
3160
|
+
context,
|
|
3161
|
+
ledger: this.taskLedger,
|
|
3162
|
+
getAdapter: () => adapter,
|
|
3163
|
+
privateConnectorPolicy: this.privateConnectorPolicy,
|
|
3164
|
+
rawConnectorScope: this.getExecutionState().envelope.scope.raw_connectors,
|
|
3165
|
+
isPartitionVisible: this.currentOwnerPartitionVisibility(),
|
|
3166
|
+
});
|
|
3167
|
+
return { success: true, ...result };
|
|
3168
|
+
}
|
|
2944
3169
|
case 'task_external_bind': {
|
|
2945
3170
|
if (!this.taskLedger) {
|
|
2946
3171
|
return { success: false, error: 'Task ledger not configured' };
|
|
2947
3172
|
}
|
|
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
3173
|
try {
|
|
2954
3174
|
(0, external_lifecycle_candidates_js_1.validateExternalLifecycleDecision)('binding', input);
|
|
2955
3175
|
}
|
|
2956
3176
|
catch (error) {
|
|
2957
3177
|
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
3178
|
}
|
|
2959
|
-
const
|
|
3179
|
+
const source = await this.externalCandidateSource(input.candidate_id);
|
|
3180
|
+
const attemptId = source.kind === 'board' ? source.attemptId : source.context.workOrderAttemptId;
|
|
3181
|
+
const modelRunId = source.kind === 'owner_run'
|
|
3182
|
+
? source.context.modelRunId
|
|
3183
|
+
: this.getExecutionState().modelRunId;
|
|
2960
3184
|
let candidate;
|
|
2961
3185
|
try {
|
|
2962
|
-
candidate = this.taskLedger.
|
|
3186
|
+
candidate = this.taskLedger.loadExternalCandidate(source, input.candidate_id, 'binding');
|
|
2963
3187
|
}
|
|
2964
3188
|
catch (error) {
|
|
2965
3189
|
throw new types_js_1.AgentError(error instanceof Error
|
|
2966
3190
|
? error.message
|
|
2967
3191
|
: 'task_external_bind candidate is unavailable', 'WORKORDER_SUPERSEDED', error instanceof Error ? error : undefined, false);
|
|
2968
3192
|
}
|
|
2969
|
-
|
|
3193
|
+
if (this.getExecutionState().envelope &&
|
|
3194
|
+
!this.currentOwnerPartitionVisibility()({
|
|
3195
|
+
connector: candidate.connector,
|
|
3196
|
+
channel: candidate.channelPartition,
|
|
3197
|
+
})) {
|
|
3198
|
+
throw new Error('External candidate source is no longer visible under the current owner grant');
|
|
3199
|
+
}
|
|
3200
|
+
const receipt = this.taskLedger.applyExternalBindingDecision(source, input, {
|
|
2970
3201
|
runId: modelRunId,
|
|
2971
3202
|
workOrderAttemptId: attemptId,
|
|
2972
3203
|
causeEventIds: [candidate.eventId],
|
|
@@ -2984,28 +3215,34 @@ class GatewayToolExecutor {
|
|
|
2984
3215
|
if (!this.taskLedger) {
|
|
2985
3216
|
return { success: false, error: 'Task ledger not configured' };
|
|
2986
3217
|
}
|
|
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
3218
|
try {
|
|
2993
3219
|
(0, external_lifecycle_candidates_js_1.validateExternalLifecycleDecision)('lifecycle', input);
|
|
2994
3220
|
}
|
|
2995
3221
|
catch (error) {
|
|
2996
3222
|
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
3223
|
}
|
|
2998
|
-
const
|
|
3224
|
+
const source = await this.externalCandidateSource(input.candidate_id);
|
|
3225
|
+
const attemptId = source.kind === 'board' ? source.attemptId : source.context.workOrderAttemptId;
|
|
3226
|
+
const modelRunId = source.kind === 'owner_run'
|
|
3227
|
+
? source.context.modelRunId
|
|
3228
|
+
: this.getExecutionState().modelRunId;
|
|
2999
3229
|
let candidate;
|
|
3000
3230
|
try {
|
|
3001
|
-
candidate = this.taskLedger.
|
|
3231
|
+
candidate = this.taskLedger.loadExternalCandidate(source, input.candidate_id, 'lifecycle');
|
|
3002
3232
|
}
|
|
3003
3233
|
catch (error) {
|
|
3004
3234
|
throw new types_js_1.AgentError(error instanceof Error
|
|
3005
3235
|
? error.message
|
|
3006
3236
|
: 'task_lifecycle_reconcile candidate is unavailable', 'WORKORDER_SUPERSEDED', error instanceof Error ? error : undefined, false);
|
|
3007
3237
|
}
|
|
3008
|
-
|
|
3238
|
+
if (this.getExecutionState().envelope &&
|
|
3239
|
+
!this.currentOwnerPartitionVisibility()({
|
|
3240
|
+
connector: candidate.connector,
|
|
3241
|
+
channel: candidate.channelPartition,
|
|
3242
|
+
})) {
|
|
3243
|
+
throw new Error('External candidate source is no longer visible under the current owner grant');
|
|
3244
|
+
}
|
|
3245
|
+
const receipt = this.taskLedger.applyExternalLifecycleDecision(source, input, {
|
|
3009
3246
|
runId: modelRunId,
|
|
3010
3247
|
workOrderAttemptId: attemptId,
|
|
3011
3248
|
causeEventIds: [candidate.eventId],
|
|
@@ -3041,20 +3278,29 @@ class GatewayToolExecutor {
|
|
|
3041
3278
|
if (completionCriteria.trim().length > task_ledger_js_1.TASK_COMPLETION_CRITERIA_MAX_LENGTH) {
|
|
3042
3279
|
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
3280
|
}
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
this.
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3281
|
+
const { creation_key: creationKey, ...taskInput } = rawTaskCreate;
|
|
3282
|
+
const create = () => this.taskLedger.create(taskInput, {
|
|
3283
|
+
runId: this.getExecutionState().modelRunId ?? null,
|
|
3284
|
+
workOrderAttemptId: this.getExecutionState().workorderAttemptId,
|
|
3285
|
+
requiresExpectedRevision: this.getExecutionState().workorderAttemptId !== undefined,
|
|
3286
|
+
causeEventIds: this.getExecutionState().causeEventIds,
|
|
3287
|
+
causeKind: this.getExecutionState().source === 'operator' ? 'clock' : 'owner_message',
|
|
3288
|
+
});
|
|
3289
|
+
if (this.ownerActionEffectLedger) {
|
|
3290
|
+
if (typeof creationKey !== 'string' ||
|
|
3291
|
+
!creationKey.trim() ||
|
|
3292
|
+
creationKey.length > 200) {
|
|
3293
|
+
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);
|
|
3294
|
+
}
|
|
3295
|
+
const context = await this.requireOwnerActionContext();
|
|
3296
|
+
const receipt = this.ownerActionEffectLedger.atomic(context, `task:${creationKey}`, 'task_create', taskInput, () => ({ taskId: create().id }));
|
|
3297
|
+
const task = this.taskLedger.getById(Number(receipt.taskId));
|
|
3298
|
+
if (!task) {
|
|
3299
|
+
throw new Error('Created task receipt references a missing task');
|
|
3300
|
+
}
|
|
3301
|
+
return { success: true, task: (0, task_list_views_js_1.serializeTaskToolRecord)(task) };
|
|
3302
|
+
}
|
|
3303
|
+
return { success: true, task: (0, task_list_views_js_1.serializeTaskToolRecord)(create()) };
|
|
3058
3304
|
}
|
|
3059
3305
|
case 'task_reclassify': {
|
|
3060
3306
|
if (!this.taskLedger) {
|
|
@@ -3117,9 +3363,6 @@ class GatewayToolExecutor {
|
|
|
3117
3363
|
'Use latest_event for input; responses return latestEvent.', 'TOOL_ERROR', undefined, false);
|
|
3118
3364
|
}
|
|
3119
3365
|
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
3366
|
const criteria = rawTaskUpdate.completion_criteria;
|
|
3124
3367
|
if (typeof criteria !== 'string' ||
|
|
3125
3368
|
criteria.trim().length === 0 ||
|
|
@@ -3155,23 +3398,47 @@ class GatewayToolExecutor {
|
|
|
3155
3398
|
}
|
|
3156
3399
|
const parsed = JSON.parse(packet.packet_json);
|
|
3157
3400
|
const selectedRaw = parsed.selected_evidence?.find((item) => item.ref?.kind === 'raw' && item.ref.raw_id === reviewAnchorRef)?.ref;
|
|
3401
|
+
if (!selectedRaw) {
|
|
3402
|
+
return {
|
|
3403
|
+
success: false,
|
|
3404
|
+
code: 'review_anchor_not_selected',
|
|
3405
|
+
error: 'The review anchor is not raw evidence selected in this context packet.',
|
|
3406
|
+
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.',
|
|
3407
|
+
};
|
|
3408
|
+
}
|
|
3158
3409
|
const task = this.taskLedger.getById(id);
|
|
3159
3410
|
const connector = selectedRaw?.connector;
|
|
3160
3411
|
const channelId = selectedRaw?.channel_id;
|
|
3161
3412
|
const sourceChannel = typeof connector === 'string' && typeof channelId === 'string'
|
|
3162
3413
|
? `${connector}:${channelId}`
|
|
3163
3414
|
: null;
|
|
3164
|
-
if (!task || !sourceChannel
|
|
3165
|
-
throw new types_js_1.AgentError('task_update review anchor
|
|
3415
|
+
if (!task || !sourceChannel) {
|
|
3416
|
+
throw new types_js_1.AgentError('task_update review anchor has no verified source identity', 'TOOL_ERROR', undefined, false);
|
|
3417
|
+
}
|
|
3418
|
+
const visible = this.currentOwnerPartitionVisibility();
|
|
3419
|
+
const taskSourceSeparator = task.sourceChannel?.indexOf(':') ?? -1;
|
|
3420
|
+
if (!visible({ connector: connector, channel: channelId }) ||
|
|
3421
|
+
(task.sourceChannel &&
|
|
3422
|
+
(taskSourceSeparator < 1 ||
|
|
3423
|
+
!visible({
|
|
3424
|
+
connector: task.sourceChannel.slice(0, taskSourceSeparator),
|
|
3425
|
+
channel: task.sourceChannel.slice(taskSourceSeparator + 1),
|
|
3426
|
+
})))) {
|
|
3427
|
+
return {
|
|
3428
|
+
success: false,
|
|
3429
|
+
code: 'review_source_not_authorized',
|
|
3430
|
+
error: 'Current owner grants do not authorize the task source and review evidence source.',
|
|
3431
|
+
};
|
|
3166
3432
|
}
|
|
3167
|
-
const adapter = (
|
|
3433
|
+
const adapter = (this.connectorEventAdapter ??
|
|
3434
|
+
(await getContextPacketLookupAdapter()));
|
|
3168
3435
|
const anchor = adapter
|
|
3169
3436
|
.prepare(`SELECT source_connector, channel,
|
|
3170
3437
|
COALESCE(event_datetime, source_timestamp_ms) AS source_timestamp_ms
|
|
3171
3438
|
FROM connector_event_index
|
|
3172
3439
|
WHERE event_index_id = ?
|
|
3173
3440
|
LIMIT 1`)
|
|
3174
|
-
.
|
|
3441
|
+
.all(reviewAnchorRef)[0];
|
|
3175
3442
|
if (!anchor ||
|
|
3176
3443
|
anchor.source_connector !== connector ||
|
|
3177
3444
|
anchor.channel !== channelId ||
|
|
@@ -3264,8 +3531,8 @@ class GatewayToolExecutor {
|
|
|
3264
3531
|
(effectInput.outcome === 'deferred'
|
|
3265
3532
|
? Array.isArray(packet.source_refs) &&
|
|
3266
3533
|
packet.source_refs.length > 0 &&
|
|
3267
|
-
!temporalPacketReferencesBoundSource(context, packet.source_refs, reviewWindow)
|
|
3268
|
-
: !temporalPacketReferencesBoundSource(context, packet.source_refs, reviewWindow))) {
|
|
3534
|
+
!temporalPacketReferencesBoundSource(context, packet.source_refs, reviewWindow, this.temporalCorroborationVisible)
|
|
3535
|
+
: !temporalPacketReferencesBoundSource(context, packet.source_refs, reviewWindow, this.temporalCorroborationVisible))) {
|
|
3269
3536
|
throw new types_js_1.AgentError('task_temporal_reconcile context packet is not bound to the active task source', 'TOOL_ERROR', undefined, false);
|
|
3270
3537
|
}
|
|
3271
3538
|
const evidence = {
|
|
@@ -3393,6 +3660,61 @@ class GatewayToolExecutor {
|
|
|
3393
3660
|
return { success: false, error: 'Failed to read file: attachment extraction failed' };
|
|
3394
3661
|
}
|
|
3395
3662
|
}
|
|
3663
|
+
async executeOwnerWorkspaceEffect(kind, intent, execute) {
|
|
3664
|
+
const ledger = this.ownerActionEffectLedger;
|
|
3665
|
+
if (!ledger) {
|
|
3666
|
+
try {
|
|
3667
|
+
const { effectStarted: _notStarted, ...result } = await execute();
|
|
3668
|
+
return result;
|
|
3669
|
+
}
|
|
3670
|
+
catch (error) {
|
|
3671
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
const context = await this.requireOwnerActionContext();
|
|
3675
|
+
const digest = (0, crypto_1.createHash)('sha256').update((0, owner_action_effects_js_1.canonicalOwnerActionJson)(intent)).digest('hex');
|
|
3676
|
+
const actionKey = `${kind}:${digest}`;
|
|
3677
|
+
const reserved = ledger.begin(context, actionKey, kind, { inputSha256: digest });
|
|
3678
|
+
if (reserved.state === 'confirmed') {
|
|
3679
|
+
return {
|
|
3680
|
+
success: true,
|
|
3681
|
+
output: 'This exact action already completed in this occurrence; it was not executed again.',
|
|
3682
|
+
};
|
|
3683
|
+
}
|
|
3684
|
+
if (reserved.state !== 'execute') {
|
|
3685
|
+
throw new types_js_1.AgentError('Prior workspace effect needs reconciliation before replay', 'CODE_ACT_MUTATION_OUTCOME_UNKNOWN', undefined, false);
|
|
3686
|
+
}
|
|
3687
|
+
try {
|
|
3688
|
+
const result = await execute();
|
|
3689
|
+
if (!result.success && result.effectStarted === false) {
|
|
3690
|
+
ledger.releaseUnstarted(context, actionKey, kind);
|
|
3691
|
+
const { effectStarted: _notStarted, ...failure } = result;
|
|
3692
|
+
return failure;
|
|
3693
|
+
}
|
|
3694
|
+
if (!result.success) {
|
|
3695
|
+
throw new Error('Workspace effect returned failure; partial effects may exist');
|
|
3696
|
+
}
|
|
3697
|
+
ledger.confirm(context, actionKey, kind, { success: true });
|
|
3698
|
+
return result;
|
|
3699
|
+
}
|
|
3700
|
+
catch (error) {
|
|
3701
|
+
// A spawn failure proves the child never ran. A nonzero exit does NOT:
|
|
3702
|
+
// a command may write data before exiting with an error.
|
|
3703
|
+
const spawnError = error;
|
|
3704
|
+
if ((kind === 'Bash' || kind === 'obsidian') &&
|
|
3705
|
+
spawnError.code === 'ENOENT' &&
|
|
3706
|
+
typeof spawnError.syscall === 'string' &&
|
|
3707
|
+
spawnError.syscall.startsWith('spawn')) {
|
|
3708
|
+
ledger.releaseUnstarted(context, actionKey, kind);
|
|
3709
|
+
return {
|
|
3710
|
+
success: false,
|
|
3711
|
+
error: 'Executable or working directory is unavailable; no process started.',
|
|
3712
|
+
};
|
|
3713
|
+
}
|
|
3714
|
+
ledger.markUnknown(context, actionKey, kind, 'workspace effect did not settle successfully');
|
|
3715
|
+
throw new types_js_1.AgentError('Workspace effect outcome requires reconciliation', 'CODE_ACT_MUTATION_OUTCOME_UNKNOWN', error instanceof Error ? error : undefined, false);
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3396
3718
|
/**
|
|
3397
3719
|
* Execute Write tool - Write content to a file
|
|
3398
3720
|
* Checks path permissions based on current AgentContext
|
|
@@ -3422,15 +3744,12 @@ class GatewayToolExecutor {
|
|
|
3422
3744
|
return { success: false, error: `Access denied: Can only write files to ${mamaDir}` };
|
|
3423
3745
|
}
|
|
3424
3746
|
}
|
|
3425
|
-
|
|
3747
|
+
return this.executeOwnerWorkspaceEffect('Write', { path: (0, path_1.resolve)(expandedPath), content }, async () => {
|
|
3426
3748
|
const dir = (0, path_1.dirname)(expandedPath);
|
|
3427
3749
|
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
3428
3750
|
(0, fs_1.writeFileSync)(expandedPath, content, 'utf-8');
|
|
3429
3751
|
return { success: true };
|
|
3430
|
-
}
|
|
3431
|
-
catch (err) {
|
|
3432
|
-
return { success: false, error: `Failed to write file: ${err}` };
|
|
3433
|
-
}
|
|
3752
|
+
});
|
|
3434
3753
|
}
|
|
3435
3754
|
/**
|
|
3436
3755
|
* Execute Bash tool - Execute bash command
|
|
@@ -3545,36 +3864,25 @@ class GatewayToolExecutor {
|
|
|
3545
3864
|
};
|
|
3546
3865
|
}
|
|
3547
3866
|
}
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
success: true,
|
|
3558
|
-
|
|
3559
|
-
};
|
|
3560
|
-
}
|
|
3561
|
-
try {
|
|
3867
|
+
return this.executeOwnerWorkspaceEffect('Bash', { command, cwd: (0, path_1.resolve)(cwd) }, async () => {
|
|
3868
|
+
// A deferred restart is still a non-replayable shell effect.
|
|
3869
|
+
const restartPattern = /systemctl\s+--user\s+restart\s+mama-os/i;
|
|
3870
|
+
if (restartPattern.test(command)) {
|
|
3871
|
+
const child = (0, child_process_1.spawn)('bash', ['-c', 'sleep 3 && systemctl --user restart mama-os'], {
|
|
3872
|
+
detached: true,
|
|
3873
|
+
stdio: 'ignore',
|
|
3874
|
+
});
|
|
3875
|
+
child.unref();
|
|
3876
|
+
return { success: true, output: 'mama-os restart will execute in 3 seconds.' };
|
|
3877
|
+
}
|
|
3562
3878
|
const output = (0, child_process_1.execSync)(command, {
|
|
3563
|
-
cwd
|
|
3879
|
+
cwd,
|
|
3564
3880
|
encoding: 'utf-8',
|
|
3565
3881
|
maxBuffer: 10 * 1024 * 1024,
|
|
3566
3882
|
timeout: 60000,
|
|
3567
3883
|
});
|
|
3568
3884
|
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
|
-
}
|
|
3885
|
+
});
|
|
3578
3886
|
}
|
|
3579
3887
|
/**
|
|
3580
3888
|
* Execute discord_send tool - Send message/file to Discord channel
|
|
@@ -3583,10 +3891,10 @@ class GatewayToolExecutor {
|
|
|
3583
3891
|
async executeDiscordSend(input) {
|
|
3584
3892
|
const { channel_id, message, image_path, file_path } = input;
|
|
3585
3893
|
if (!channel_id) {
|
|
3586
|
-
return { success: false, error: 'channel_id is required' };
|
|
3894
|
+
return { success: false, effectStarted: false, error: 'channel_id is required' };
|
|
3587
3895
|
}
|
|
3588
3896
|
if (!this.discordGateway) {
|
|
3589
|
-
return { success: false, error: 'Discord gateway not configured' };
|
|
3897
|
+
return { success: false, effectStarted: false, error: 'Discord gateway not configured' };
|
|
3590
3898
|
}
|
|
3591
3899
|
try {
|
|
3592
3900
|
// file_path takes precedence, fallback to image_path for backwards compatibility
|
|
@@ -3598,7 +3906,11 @@ class GatewayToolExecutor {
|
|
|
3598
3906
|
await this.discordGateway.sendMessage(channel_id, message);
|
|
3599
3907
|
}
|
|
3600
3908
|
else {
|
|
3601
|
-
return {
|
|
3909
|
+
return {
|
|
3910
|
+
success: false,
|
|
3911
|
+
effectStarted: false,
|
|
3912
|
+
error: 'Either message, file_path, or image_path is required',
|
|
3913
|
+
};
|
|
3602
3914
|
}
|
|
3603
3915
|
return { success: true };
|
|
3604
3916
|
}
|
|
@@ -3612,10 +3924,10 @@ class GatewayToolExecutor {
|
|
|
3612
3924
|
async executeSlackSend(input) {
|
|
3613
3925
|
const { channel_id, message, file_path } = input;
|
|
3614
3926
|
if (!channel_id) {
|
|
3615
|
-
return { success: false, error: 'channel_id is required' };
|
|
3927
|
+
return { success: false, effectStarted: false, error: 'channel_id is required' };
|
|
3616
3928
|
}
|
|
3617
3929
|
if (!this.slackGateway) {
|
|
3618
|
-
return { success: false, error: 'Slack gateway not configured' };
|
|
3930
|
+
return { success: false, effectStarted: false, error: 'Slack gateway not configured' };
|
|
3619
3931
|
}
|
|
3620
3932
|
try {
|
|
3621
3933
|
if (file_path) {
|
|
@@ -3625,7 +3937,11 @@ class GatewayToolExecutor {
|
|
|
3625
3937
|
await this.slackGateway.sendMessage(channel_id, message);
|
|
3626
3938
|
}
|
|
3627
3939
|
else {
|
|
3628
|
-
return {
|
|
3940
|
+
return {
|
|
3941
|
+
success: false,
|
|
3942
|
+
effectStarted: false,
|
|
3943
|
+
error: 'Either message or file_path is required',
|
|
3944
|
+
};
|
|
3629
3945
|
}
|
|
3630
3946
|
return { success: true };
|
|
3631
3947
|
}
|
|
@@ -3645,7 +3961,8 @@ class GatewayToolExecutor {
|
|
|
3645
3961
|
return { success: false, error: 'Telegram gateway not configured' };
|
|
3646
3962
|
}
|
|
3647
3963
|
const sourceMessageRef = this.getExecutionState().sourceMessageRef;
|
|
3648
|
-
const
|
|
3964
|
+
const legacyOwnerEvent = sourceMessageRef?.startsWith('owner-event:') === true;
|
|
3965
|
+
const ownerEvent = legacyOwnerEvent || this.ownerActionEffectLedger !== null;
|
|
3649
3966
|
let ownerEffect = null;
|
|
3650
3967
|
let ownerLedger = null;
|
|
3651
3968
|
let ownerIntent = null;
|
|
@@ -3655,9 +3972,27 @@ class GatewayToolExecutor {
|
|
|
3655
3972
|
// `-> error/missing receipt -> unknown -> reconcile-only
|
|
3656
3973
|
try {
|
|
3657
3974
|
if (ownerEvent) {
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3975
|
+
if (legacyOwnerEvent) {
|
|
3976
|
+
ownerEffect = this.requireOwnerEventEffect(delivery_key, 'telegram_send');
|
|
3977
|
+
ownerLedger = this.requireOwnerEventEffectLedger();
|
|
3978
|
+
idempotencyKey = `${sourceMessageRef}:telegram:${delivery_key}`;
|
|
3979
|
+
}
|
|
3980
|
+
else {
|
|
3981
|
+
const context = await this.requireOwnerActionContext();
|
|
3982
|
+
const identity = [
|
|
3983
|
+
sourceMessageRef ?? context.occurrenceKey,
|
|
3984
|
+
chat_id,
|
|
3985
|
+
message ?? null,
|
|
3986
|
+
file_path ?? null,
|
|
3987
|
+
sticker_emotion ?? null,
|
|
3988
|
+
];
|
|
3989
|
+
if (delivery_key !== undefined) {
|
|
3990
|
+
identity.push(delivery_key);
|
|
3991
|
+
}
|
|
3992
|
+
idempotencyKey = (0, crypto_1.createHash)('sha256').update(JSON.stringify(identity)).digest('hex');
|
|
3993
|
+
ownerEffect = { batchId: 0, actionKey: `telegram:${delivery_key ?? idempotencyKey}` };
|
|
3994
|
+
ownerLedger = this.ownerEffectPort(context);
|
|
3995
|
+
}
|
|
3661
3996
|
const existing = ownerLedger.inspect(ownerEffect.batchId, ownerEffect.actionKey, 'telegram_send');
|
|
3662
3997
|
if (existing && !(0, owner_event_effects_js_1.isOwnerEventTelegramIntentV1)(existing.intent)) {
|
|
3663
3998
|
return existing.state === 'confirmed'
|
|
@@ -3679,9 +4014,20 @@ class GatewayToolExecutor {
|
|
|
3679
4014
|
if (reservation.state === 'confirmed')
|
|
3680
4015
|
return { success: true };
|
|
3681
4016
|
if (reservation.state === 'reconcile') {
|
|
4017
|
+
const variants = ownerIntent.variant === 'image' ? ['image', 'file'] : [ownerIntent.variant];
|
|
4018
|
+
for (const variant of variants) {
|
|
4019
|
+
const receipt = this.telegramGateway.readOutboundDeliveryReceipt?.(idempotencyKey, variant);
|
|
4020
|
+
if (isExactTelegramDeliveryReceipt(receipt, idempotencyKey, variant)) {
|
|
4021
|
+
ownerLedger.confirm(ownerEffect.batchId, ownerEffect.actionKey, 'telegram_send', {
|
|
4022
|
+
version: 1,
|
|
4023
|
+
...receipt,
|
|
4024
|
+
});
|
|
4025
|
+
return { success: true };
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
3682
4028
|
return {
|
|
3683
4029
|
success: false,
|
|
3684
|
-
error: '
|
|
4030
|
+
error: 'Telegram delivery outcome is unconfirmed; reconcile the existing delivery before sending again.',
|
|
3685
4031
|
};
|
|
3686
4032
|
}
|
|
3687
4033
|
}
|
|
@@ -3895,11 +4241,14 @@ class GatewayToolExecutor {
|
|
|
3895
4241
|
async executeWebchatSend(input) {
|
|
3896
4242
|
const { message, file_path } = input;
|
|
3897
4243
|
if (!message && !file_path) {
|
|
3898
|
-
return {
|
|
4244
|
+
return {
|
|
4245
|
+
success: false,
|
|
4246
|
+
effectStarted: false,
|
|
4247
|
+
error: 'Either message or file_path is required',
|
|
4248
|
+
};
|
|
3899
4249
|
}
|
|
3900
4250
|
try {
|
|
3901
4251
|
const outboundDir = (0, path_1.join)((0, os_1.homedir)(), '.mama', 'workspace', 'media', 'outbound');
|
|
3902
|
-
(0, fs_1.mkdirSync)(outboundDir, { recursive: true });
|
|
3903
4252
|
if (file_path) {
|
|
3904
4253
|
// Expand ~ to home directory
|
|
3905
4254
|
const homeDir = (0, os_1.homedir)();
|
|
@@ -3909,7 +4258,7 @@ class GatewayToolExecutor {
|
|
|
3909
4258
|
// Check path permission based on role
|
|
3910
4259
|
const pathPermission = this.checkPathPermission(expandedPath);
|
|
3911
4260
|
if (!pathPermission.allowed) {
|
|
3912
|
-
return { success: false, error: pathPermission.error };
|
|
4261
|
+
return { success: false, effectStarted: false, error: pathPermission.error };
|
|
3913
4262
|
}
|
|
3914
4263
|
// Fallback security for contexts without path restrictions:
|
|
3915
4264
|
// Only allow reading from ~/.mama/ directory
|
|
@@ -3922,17 +4271,19 @@ class GatewayToolExecutor {
|
|
|
3922
4271
|
if (rel.startsWith('..') || (0, path_1.isAbsolute)(rel)) {
|
|
3923
4272
|
return {
|
|
3924
4273
|
success: false,
|
|
4274
|
+
effectStarted: false,
|
|
3925
4275
|
error: `Access denied: Can only copy files from ${mamaDir}`,
|
|
3926
4276
|
};
|
|
3927
4277
|
}
|
|
3928
4278
|
}
|
|
3929
4279
|
if (!(0, fs_1.existsSync)(expandedPath)) {
|
|
3930
|
-
return { success: false, error: `File not found: ${expandedPath}` };
|
|
4280
|
+
return { success: false, effectStarted: false, error: `File not found: ${expandedPath}` };
|
|
3931
4281
|
}
|
|
3932
4282
|
// Copy file to outbound directory with timestamp prefix
|
|
3933
4283
|
const baseName = (0, path_1.basename)(expandedPath) || 'file';
|
|
3934
4284
|
const outName = `${Date.now()}_${baseName}`;
|
|
3935
4285
|
const outPath = (0, path_1.join)(outboundDir, outName);
|
|
4286
|
+
(0, fs_1.mkdirSync)(outboundDir, { recursive: true });
|
|
3936
4287
|
(0, fs_1.copyFileSync)(expandedPath, outPath);
|
|
3937
4288
|
const viewerPath = `~/.mama/workspace/media/outbound/${outName}`;
|
|
3938
4289
|
return {
|
|
@@ -3956,10 +4307,8 @@ class GatewayToolExecutor {
|
|
|
3956
4307
|
}
|
|
3957
4308
|
/**
|
|
3958
4309
|
* schedule_upcoming (M8 P4): read the calendar connector's raw store and
|
|
3959
|
-
* return
|
|
3960
|
-
*
|
|
3961
|
-
* v1 limits (documented in the tool description): no recurrence expansion,
|
|
3962
|
-
* no cancellation tracking.
|
|
4310
|
+
* return intervals overlapping [now, now+days] plus a compact text digest.
|
|
4311
|
+
* Reads a bounded page from the local snapshot and never calls the upstream API.
|
|
3963
4312
|
*/
|
|
3964
4313
|
scheduleDb = null;
|
|
3965
4314
|
executeScheduleUpcoming(input) {
|
|
@@ -3978,17 +4327,178 @@ class GatewayToolExecutor {
|
|
|
3978
4327
|
this.scheduleDb = new sqlite_js_1.default(dbPath);
|
|
3979
4328
|
this.scheduleDb.prepare('PRAGMA busy_timeout = 5000').get();
|
|
3980
4329
|
}
|
|
3981
|
-
|
|
4330
|
+
let offset = 0;
|
|
4331
|
+
let now = Date.now();
|
|
4332
|
+
let cursorReadVersion;
|
|
4333
|
+
if (input.cursor) {
|
|
4334
|
+
try {
|
|
4335
|
+
const decoded = JSON.parse(Buffer.from(input.cursor, 'base64url').toString('utf8'));
|
|
4336
|
+
if (!Number.isInteger(decoded.offset) ||
|
|
4337
|
+
typeof decoded.offset !== 'number' ||
|
|
4338
|
+
decoded.offset < 1 ||
|
|
4339
|
+
decoded.days !== days ||
|
|
4340
|
+
typeof decoded.now !== 'number' ||
|
|
4341
|
+
typeof decoded.readVersion !== 'string') {
|
|
4342
|
+
throw new Error('invalid fields');
|
|
4343
|
+
}
|
|
4344
|
+
offset = decoded.offset;
|
|
4345
|
+
now = decoded.now;
|
|
4346
|
+
cursorReadVersion = decoded.readVersion;
|
|
4347
|
+
}
|
|
4348
|
+
catch {
|
|
4349
|
+
return { success: false, error: 'Invalid schedule cursor; restart from page one' };
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
3982
4352
|
const until = now + days * 86_400_000;
|
|
3983
|
-
let rows;
|
|
3984
4353
|
try {
|
|
3985
|
-
|
|
3986
|
-
.prepare(`
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
4354
|
+
const readSnapshot = this.scheduleDb.transaction(() => {
|
|
4355
|
+
const latestRows = this.scheduleDb.prepare(`WITH ranked AS (
|
|
4356
|
+
SELECT id, source_id, channel, content, timestamp, metadata, source_cursor, created_at,
|
|
4357
|
+
ROW_NUMBER() OVER (
|
|
4358
|
+
PARTITION BY COALESCE(json_extract(metadata, '$.eventId'), source_id)
|
|
4359
|
+
ORDER BY CASE
|
|
4360
|
+
WHEN json_extract(metadata, '$.eventId') IS NOT NULL THEN 1
|
|
4361
|
+
ELSE 0
|
|
4362
|
+
END DESC,
|
|
4363
|
+
COALESCE((julianday(source_cursor) - 2440587.5) * 86400000,
|
|
4364
|
+
(julianday(json_extract(metadata, '$.observedAt')) - 2440587.5) * 86400000,
|
|
4365
|
+
created_at) DESC,
|
|
4366
|
+
id DESC
|
|
4367
|
+
) AS observation_rank
|
|
4368
|
+
FROM raw_items
|
|
4369
|
+
WHERE source = 'calendar'
|
|
4370
|
+
)
|
|
4371
|
+
SELECT id, source_id, channel, content, timestamp, metadata, source_cursor, created_at
|
|
4372
|
+
FROM ranked
|
|
4373
|
+
WHERE observation_rank = 1`).all();
|
|
4374
|
+
const selected = latestRows.flatMap((row) => {
|
|
4375
|
+
let metadata = {};
|
|
4376
|
+
try {
|
|
4377
|
+
metadata = row.metadata ? JSON.parse(row.metadata) : {};
|
|
4378
|
+
}
|
|
4379
|
+
catch {
|
|
4380
|
+
metadata = {};
|
|
4381
|
+
}
|
|
4382
|
+
const metaStart = metadata.start ?? metadata.startTime ?? metadata.start_time;
|
|
4383
|
+
const metaEnd = metadata.end ?? metadata.endTime ?? metadata.end_time;
|
|
4384
|
+
const allDay = metadata.allDay === true;
|
|
4385
|
+
const timeZone = typeof metadata.timeZone === 'string' ? metadata.timeZone : 'UTC';
|
|
4386
|
+
// Contain a bad all-day date to this row: startOfTaskDate throws on an unresolvable value,
|
|
4387
|
+
// and inside flatMap that would abort the whole schedule_upcoming read and clear scheduleDb.
|
|
4388
|
+
// NaN lets the finite-number guard below skip only this row.
|
|
4389
|
+
const dayStart = (value) => {
|
|
4390
|
+
try {
|
|
4391
|
+
return (0, temporal_reconcile_js_1.startOfTaskDate)(value, null, timeZone);
|
|
4392
|
+
}
|
|
4393
|
+
catch {
|
|
4394
|
+
return Number.NaN;
|
|
4395
|
+
}
|
|
4396
|
+
};
|
|
4397
|
+
const startMs = allDay && typeof metaStart === 'string'
|
|
4398
|
+
? dayStart(metaStart)
|
|
4399
|
+
: typeof metaStart === 'string' || typeof metaStart === 'number'
|
|
4400
|
+
? new Date(metaStart).getTime()
|
|
4401
|
+
: row.timestamp;
|
|
4402
|
+
const endMs = allDay && typeof metaEnd === 'string'
|
|
4403
|
+
? dayStart(metaEnd)
|
|
4404
|
+
: typeof metaEnd === 'string' || typeof metaEnd === 'number'
|
|
4405
|
+
? new Date(metaEnd).getTime()
|
|
4406
|
+
: startMs;
|
|
4407
|
+
if (metadata.status === 'cancelled' ||
|
|
4408
|
+
!Number.isFinite(startMs) ||
|
|
4409
|
+
!Number.isFinite(endMs) ||
|
|
4410
|
+
startMs > until ||
|
|
4411
|
+
endMs <= now) {
|
|
4412
|
+
return [];
|
|
4413
|
+
}
|
|
4414
|
+
const eventId = typeof metadata.eventId === 'string' ? metadata.eventId : row.source_id;
|
|
4415
|
+
const observedAt = typeof row.source_cursor === 'string' && Number.isFinite(Date.parse(row.source_cursor))
|
|
4416
|
+
? row.source_cursor
|
|
4417
|
+
: typeof metadata.observedAt === 'string' &&
|
|
4418
|
+
Number.isFinite(Date.parse(metadata.observedAt))
|
|
4419
|
+
? metadata.observedAt
|
|
4420
|
+
: null;
|
|
4421
|
+
const event = {
|
|
4422
|
+
sourceId: eventId,
|
|
4423
|
+
title: typeof metadata.summary === 'string'
|
|
4424
|
+
? metadata.summary.slice(0, 120)
|
|
4425
|
+
: ((row.content ?? '').split('\n')[0]?.slice(0, 120) ?? ''),
|
|
4426
|
+
start: new Date(Number.isFinite(startMs) ? startMs : row.timestamp).toISOString(),
|
|
4427
|
+
end: typeof metaEnd === 'string' ? metaEnd : null,
|
|
4428
|
+
status: typeof metadata.status === 'string' ? metadata.status : 'unknown',
|
|
4429
|
+
channel: row.channel,
|
|
4430
|
+
...(allDay && typeof metaStart === 'string' && typeof metaEnd === 'string'
|
|
4431
|
+
? {
|
|
4432
|
+
allDay: true,
|
|
4433
|
+
startDate: metaStart,
|
|
4434
|
+
endDateExclusive: metaEnd,
|
|
4435
|
+
timeZone,
|
|
4436
|
+
}
|
|
4437
|
+
: {}),
|
|
4438
|
+
};
|
|
4439
|
+
return [{ row, event, observedAt, startMs }];
|
|
4440
|
+
});
|
|
4441
|
+
selected.sort((left, right) => left.startMs - right.startMs || left.event.sourceId.localeCompare(right.event.sourceId));
|
|
4442
|
+
const readVersion = (0, crypto_1.createHash)('sha256')
|
|
4443
|
+
.update(JSON.stringify(selected.map(({ row, event }) => ({
|
|
4444
|
+
id: row.id,
|
|
4445
|
+
sourceId: row.source_id,
|
|
4446
|
+
content: row.content,
|
|
4447
|
+
metadata: row.metadata,
|
|
4448
|
+
sourceCursor: row.source_cursor,
|
|
4449
|
+
timestamp: row.timestamp,
|
|
4450
|
+
event,
|
|
4451
|
+
}))))
|
|
4452
|
+
.digest('hex');
|
|
4453
|
+
const observedTimes = selected
|
|
4454
|
+
.map(({ observedAt }) => (observedAt === null ? Number.NaN : Date.parse(observedAt)))
|
|
4455
|
+
.filter(Number.isFinite);
|
|
4456
|
+
const insertedTimes = selected.map(({ row }) => row.created_at).filter(Number.isFinite);
|
|
4457
|
+
return {
|
|
4458
|
+
total: selected.length,
|
|
4459
|
+
rows: selected.slice(offset, offset + 50),
|
|
4460
|
+
readVersion,
|
|
4461
|
+
observedAt: observedTimes.length > 0 ? Math.max(...observedTimes) : null,
|
|
4462
|
+
insertedAt: insertedTimes.length > 0 ? Math.max(...insertedTimes) : null,
|
|
4463
|
+
};
|
|
4464
|
+
})();
|
|
4465
|
+
if (cursorReadVersion && cursorReadVersion !== readSnapshot.readVersion) {
|
|
4466
|
+
return { success: false, error: 'Schedule snapshot changed; restart from page one' };
|
|
4467
|
+
}
|
|
4468
|
+
const events = readSnapshot.rows.map(({ event }) => event);
|
|
4469
|
+
const nextOffset = offset + events.length;
|
|
4470
|
+
const nextCursor = nextOffset < readSnapshot.total
|
|
4471
|
+
? Buffer.from(JSON.stringify({
|
|
4472
|
+
offset: nextOffset,
|
|
4473
|
+
days,
|
|
4474
|
+
now,
|
|
4475
|
+
readVersion: readSnapshot.readVersion,
|
|
4476
|
+
}), 'utf8').toString('base64url')
|
|
4477
|
+
: null;
|
|
4478
|
+
const text = events.length === 0
|
|
4479
|
+
? `(no calendar events in the next ${days} days)`
|
|
4480
|
+
: events
|
|
4481
|
+
.map((event) => `- ${event.start.slice(0, 16)} ${event.title} (${event.channel})`)
|
|
4482
|
+
.join('\n');
|
|
4483
|
+
return {
|
|
4484
|
+
success: true,
|
|
4485
|
+
events,
|
|
4486
|
+
text,
|
|
4487
|
+
total: readSnapshot.total,
|
|
4488
|
+
returned: events.length,
|
|
4489
|
+
nextCursor,
|
|
4490
|
+
observedAt: readSnapshot.observedAt === null ? null : new Date(readSnapshot.observedAt).toISOString(),
|
|
4491
|
+
insertedAt: readSnapshot.insertedAt === null ? null : new Date(readSnapshot.insertedAt).toISOString(),
|
|
4492
|
+
cacheAgeMs: readSnapshot.observedAt === null
|
|
4493
|
+
? null
|
|
4494
|
+
: Math.max(0, Date.now() - readSnapshot.observedAt),
|
|
4495
|
+
readVersion: readSnapshot.readVersion,
|
|
4496
|
+
coverage: {
|
|
4497
|
+
complete: nextCursor === null,
|
|
4498
|
+
source: 'local_snapshot',
|
|
4499
|
+
upstreamComplete: 'unknown',
|
|
4500
|
+
},
|
|
4501
|
+
};
|
|
3992
4502
|
}
|
|
3993
4503
|
catch (err) {
|
|
3994
4504
|
// Corrupt/locked store or missing table: fail closed with a readable
|
|
@@ -3999,30 +4509,6 @@ class GatewayToolExecutor {
|
|
|
3999
4509
|
error: `Calendar raw store unreadable: ${err instanceof Error ? err.message : String(err)}`,
|
|
4000
4510
|
};
|
|
4001
4511
|
}
|
|
4002
|
-
const events = rows.map((row) => {
|
|
4003
|
-
let start = row.timestamp;
|
|
4004
|
-
try {
|
|
4005
|
-
const meta = row.metadata ? JSON.parse(row.metadata) : null;
|
|
4006
|
-
const metaStart = meta?.start ?? meta?.startTime ?? meta?.start_time;
|
|
4007
|
-
if (typeof metaStart === 'string' || typeof metaStart === 'number') {
|
|
4008
|
-
const parsed = new Date(metaStart).getTime();
|
|
4009
|
-
if (Number.isFinite(parsed))
|
|
4010
|
-
start = parsed;
|
|
4011
|
-
}
|
|
4012
|
-
}
|
|
4013
|
-
catch {
|
|
4014
|
-
/* metadata is best-effort */
|
|
4015
|
-
}
|
|
4016
|
-
return {
|
|
4017
|
-
title: (row.content ?? '').split('\n')[0]?.slice(0, 120) ?? '',
|
|
4018
|
-
start: new Date(start).toISOString(),
|
|
4019
|
-
channel: row.channel,
|
|
4020
|
-
};
|
|
4021
|
-
});
|
|
4022
|
-
const text = events.length === 0
|
|
4023
|
-
? `(no calendar events in the next ${days} days)`
|
|
4024
|
-
: events.map((e) => `- ${e.start.slice(0, 16)} ${e.title} (${e.channel})`).join('\n');
|
|
4025
|
-
return { success: true, events, text };
|
|
4026
4512
|
}
|
|
4027
4513
|
/**
|
|
4028
4514
|
* Execute Obsidian CLI command on the wiki vault.
|
|
@@ -4063,28 +4549,45 @@ class GatewayToolExecutor {
|
|
|
4063
4549
|
catch (error) {
|
|
4064
4550
|
throw new types_js_1.AgentError(error instanceof Error ? error.message : 'Invalid Obsidian CLI arguments', 'TOOL_ERROR', undefined, false);
|
|
4065
4551
|
}
|
|
4066
|
-
|
|
4552
|
+
const invoke = async () => {
|
|
4067
4553
|
const { stdout } = await execFileAsync('obsidian', cliArgs, {
|
|
4068
4554
|
timeout: 15000,
|
|
4069
4555
|
encoding: 'utf-8',
|
|
4070
4556
|
maxBuffer: 1024 * 1024,
|
|
4071
4557
|
});
|
|
4072
|
-
return {
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4558
|
+
return { success: true, output: stdout.trim() };
|
|
4559
|
+
};
|
|
4560
|
+
const readCommands = new Set([
|
|
4561
|
+
'read',
|
|
4562
|
+
'daily:read',
|
|
4563
|
+
'search',
|
|
4564
|
+
'search:context',
|
|
4565
|
+
'files',
|
|
4566
|
+
'folders',
|
|
4567
|
+
'vault',
|
|
4568
|
+
'vaults',
|
|
4569
|
+
'links',
|
|
4570
|
+
'backlinks',
|
|
4571
|
+
'properties',
|
|
4572
|
+
'property:read',
|
|
4573
|
+
'aliases',
|
|
4574
|
+
'tags',
|
|
4575
|
+
]);
|
|
4576
|
+
try {
|
|
4577
|
+
const result = readCommands.has(command)
|
|
4578
|
+
? await invoke()
|
|
4579
|
+
: await this.executeOwnerWorkspaceEffect('obsidian', { command, args: args ?? {}, vault: this.obsidianVaultPath }, invoke);
|
|
4580
|
+
return result.success
|
|
4581
|
+
? { success: true, data: { output: result.output } }
|
|
4582
|
+
: result;
|
|
4076
4583
|
}
|
|
4077
|
-
catch (
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
return {
|
|
4081
|
-
success: false,
|
|
4082
|
-
error: 'Obsidian CLI unavailable (app not running). Use wiki_publish fallback.',
|
|
4083
|
-
};
|
|
4584
|
+
catch (error) {
|
|
4585
|
+
if (error instanceof types_js_1.AgentError) {
|
|
4586
|
+
throw error;
|
|
4084
4587
|
}
|
|
4085
4588
|
return {
|
|
4086
4589
|
success: false,
|
|
4087
|
-
error:
|
|
4590
|
+
error: 'Obsidian CLI unavailable or command failed; inspect the configured vault before retrying.',
|
|
4088
4591
|
};
|
|
4089
4592
|
}
|
|
4090
4593
|
}
|
|
@@ -4461,7 +4964,9 @@ class GatewayToolExecutor {
|
|
|
4461
4964
|
// prefix and causeEventIds: the agent never restates what the host knows.
|
|
4462
4965
|
if (temporalContext && this.taskLedger) {
|
|
4463
4966
|
const boundTask = this.taskLedger.getById(temporalContext.taskId);
|
|
4464
|
-
const rawChannel = boundTask?.
|
|
4967
|
+
const rawChannel = (boundTask?.status === 'review'
|
|
4968
|
+
? (boundTask.reviewAnchorSourceChannel ?? boundTask.sourceChannel)
|
|
4969
|
+
: boundTask?.sourceChannel) ?? null;
|
|
4465
4970
|
// Trimmed: a whitespace-only event id is truthy but fails ref
|
|
4466
4971
|
// normalization downstream - which would fail the WHOLE compile,
|
|
4467
4972
|
// exactly what "strictly additive" forbids.
|
|
@@ -4528,7 +5033,7 @@ class GatewayToolExecutor {
|
|
|
4528
5033
|
: undefined,
|
|
4529
5034
|
});
|
|
4530
5035
|
if (temporalContext &&
|
|
4531
|
-
!temporalPacketRawSourcesWithinBoundSource(temporalContext, result.packet.source_refs, reviewWindow)) {
|
|
5036
|
+
!temporalPacketRawSourcesWithinBoundSource(temporalContext, result.packet.source_refs, reviewWindow, this.temporalCorroborationVisible)) {
|
|
4532
5037
|
throw new Error('context_compile packet exceeds the active temporal task source');
|
|
4533
5038
|
}
|
|
4534
5039
|
if (wikiAuthority) {
|