@framers/agentos 0.11.5 → 0.11.7
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/cognition/emergent/EmergentCapabilityEngine.d.ts +23 -0
- package/dist/cognition/emergent/EmergentCapabilityEngine.d.ts.map +1 -1
- package/dist/cognition/emergent/EmergentCapabilityEngine.js +218 -64
- package/dist/cognition/emergent/EmergentCapabilityEngine.js.map +1 -1
- package/dist/cognition/emergent/EmergentToolRegistry.d.ts +41 -5
- package/dist/cognition/emergent/EmergentToolRegistry.d.ts.map +1 -1
- package/dist/cognition/emergent/EmergentToolRegistry.js +94 -19
- package/dist/cognition/emergent/EmergentToolRegistry.js.map +1 -1
- package/dist/cognition/emergent/types.d.ts +14 -2
- package/dist/cognition/emergent/types.d.ts.map +1 -1
- package/dist/cognition/emergent/types.js +10 -0
- package/dist/cognition/emergent/types.js.map +1 -1
- package/dist/core/llm/providers/implementations/OpenRouterProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/OpenRouterProvider.js +4 -2
- package/dist/core/llm/providers/implementations/OpenRouterProvider.js.map +1 -1
- package/dist/io/media/images/imageToBuffer.d.ts.map +1 -1
- package/dist/io/media/images/imageToBuffer.js +3 -2
- package/dist/io/media/images/imageToBuffer.js.map +1 -1
- package/dist/io/speech/SpeechProviderResolver.d.ts.map +1 -1
- package/dist/io/speech/SpeechProviderResolver.js +13 -12
- package/dist/io/speech/SpeechProviderResolver.js.map +1 -1
- package/dist/io/speech/SpeechRuntime.d.ts.map +1 -1
- package/dist/io/speech/SpeechRuntime.js +16 -8
- package/dist/io/speech/SpeechRuntime.js.map +1 -1
- package/package.json +1 -1
|
@@ -286,6 +286,12 @@ export declare class EmergentCapabilityEngine {
|
|
|
286
286
|
* @returns the outcome, or `undefined` when the tool is unknown.
|
|
287
287
|
*/
|
|
288
288
|
reactivateTool(toolId: string): Promise<LoadedToolOutcome | undefined>;
|
|
289
|
+
/**
|
|
290
|
+
* Run a read of stored rows and the admissions that use it. `readAt` is the
|
|
291
|
+
* point the read started at; an admission adopts only what has not changed
|
|
292
|
+
* since (see `EmergentToolRegistry.adopt`).
|
|
293
|
+
*/
|
|
294
|
+
private withRead;
|
|
289
295
|
private admitRow;
|
|
290
296
|
/**
|
|
291
297
|
* The single-row path. Every stored tool goes through it, whether the
|
|
@@ -299,6 +305,23 @@ export declare class EmergentCapabilityEngine {
|
|
|
299
305
|
*/
|
|
300
306
|
private readmit;
|
|
301
307
|
private admit;
|
|
308
|
+
/**
|
|
309
|
+
* After the host registered a tool's executable, the registry must still
|
|
310
|
+
* hold that very object. When it does not (the tool was removed, or
|
|
311
|
+
* replaced under its id, while the registration ran), the executor is
|
|
312
|
+
* brought back in line with the registry: the current tool's executable is
|
|
313
|
+
* registered (the old name's taken out first when the name changed), or
|
|
314
|
+
* the stale executable is taken out when the registry holds none. The
|
|
315
|
+
* registry is read again after every registration, so a change during the
|
|
316
|
+
* settlement is settled too, up to three rounds. A registration that fails,
|
|
317
|
+
* or a tool still changing after three rounds, leaves no executable under
|
|
318
|
+
* the name and throws: a tool that cannot be called is reported, never a
|
|
319
|
+
* stale executable left running. Returns the outcome to report, or
|
|
320
|
+
* undefined when the first registration stands.
|
|
321
|
+
*/
|
|
322
|
+
private settleRegistration;
|
|
323
|
+
/** Take a tool's executable out of the host, best-effort. */
|
|
324
|
+
private dropExecutable;
|
|
302
325
|
/**
|
|
303
326
|
* The library's reason for not running an implementation under the
|
|
304
327
|
* configuration in force, or `null`. Later steps add their checks here, so
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmergentCapabilityEngine.d.ts","sourceRoot":"","sources":["../../../src/cognition/emergent/EmergentCapabilityEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,YAAY,EAKZ,SAAS,EAET,QAAQ,EACT,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"EmergentCapabilityEngine.d.ts","sourceRoot":"","sources":["../../../src/cognition/emergent/EmergentCapabilityEngine.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* After registration the engine tracks usage and auto-promotes tools that
|
|
14
14
|
* meet the configured `EmergentConfig.promotionThreshold` criteria.
|
|
15
15
|
*/
|
|
16
|
+
import { GMI_INSTANCE_ID_PREFIX } from './types.js';
|
|
16
17
|
import { parsePersistedSource, parseRowSchemas, parseStoredRequest, requestFromImplementation, sessionFromSource, requestFromSource, sourceFromImplementation, stateSetterFromColumn, toolFromRow, } from './persisted-source.js';
|
|
17
18
|
import { normalizeAllowlist, toSandboxApis } from './capabilities.js';
|
|
18
19
|
// ============================================================================
|
|
@@ -234,6 +235,7 @@ export class EmergentCapabilityEngine {
|
|
|
234
235
|
source: `forged by agent ${context.agentId} during session ${context.sessionId}`,
|
|
235
236
|
};
|
|
236
237
|
this.registry.register(tool, 'session');
|
|
238
|
+
const registered = this.registry.generation(toolId);
|
|
237
239
|
let written;
|
|
238
240
|
try {
|
|
239
241
|
written = await this.registry.setState(toolId, 'active', null, {
|
|
@@ -246,7 +248,7 @@ export class EmergentCapabilityEngine {
|
|
|
246
248
|
// is re-derived from its source at the next load.
|
|
247
249
|
console.warn(`[agentos:emergent] could not store the request of "${request.name}" (${toolId}):`, error instanceof Error ? error.message : error);
|
|
248
250
|
}
|
|
249
|
-
if (this.registry.
|
|
251
|
+
if (this.registry.generation(toolId) !== registered) {
|
|
250
252
|
// Removed while it was being forged: nothing is registered.
|
|
251
253
|
return { success: false, error: 'the tool was removed while it was being forged' };
|
|
252
254
|
}
|
|
@@ -273,8 +275,12 @@ export class EmergentCapabilityEngine {
|
|
|
273
275
|
}
|
|
274
276
|
this.indexTool(toolId, context.agentId, context.sessionId);
|
|
275
277
|
if (this.onToolForged) {
|
|
278
|
+
// The object the registry holds (a registration stores a stamped copy;
|
|
279
|
+
// an adoption above stores the object itself), so the settlement
|
|
280
|
+
// below compares like with like.
|
|
281
|
+
const live = this.registry.get(toolId) ?? tool;
|
|
276
282
|
try {
|
|
277
|
-
await this.onToolForged(
|
|
283
|
+
await this.onToolForged(live, this.createExecutableTool(live));
|
|
278
284
|
}
|
|
279
285
|
catch (error) {
|
|
280
286
|
this.registry.remove(toolId);
|
|
@@ -284,6 +290,20 @@ export class EmergentCapabilityEngine {
|
|
|
284
290
|
error: error instanceof Error ? error.message : 'Failed to activate forged tool.',
|
|
285
291
|
};
|
|
286
292
|
}
|
|
293
|
+
let settled;
|
|
294
|
+
try {
|
|
295
|
+
settled = await this.settleRegistration(live);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
return {
|
|
299
|
+
success: false,
|
|
300
|
+
error: error instanceof Error ? error.message : 'the forged tool could not be registered',
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
if (settled && settled.reason === 'removed') {
|
|
304
|
+
// Removed while the host was registering it: nothing is registered.
|
|
305
|
+
return { success: false, error: 'the tool was removed while it was being forged' };
|
|
306
|
+
}
|
|
287
307
|
}
|
|
288
308
|
return {
|
|
289
309
|
success: true,
|
|
@@ -328,6 +348,11 @@ export class EmergentCapabilityEngine {
|
|
|
328
348
|
if (tool.tier !== 'session') {
|
|
329
349
|
return null;
|
|
330
350
|
}
|
|
351
|
+
// An owner with the reserved instance-id prefix is never promoted: an
|
|
352
|
+
// agent-tier row with that owner reads as one an earlier release wrote.
|
|
353
|
+
if (tool.createdBy.startsWith(GMI_INSTANCE_ID_PREFIX)) {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
331
356
|
// Check thresholds.
|
|
332
357
|
const { uses, confidence } = this.config.promotionThreshold;
|
|
333
358
|
if (tool.usageStats.totalUses < uses || tool.usageStats.confidenceScore < confidence) {
|
|
@@ -403,6 +428,17 @@ export class EmergentCapabilityEngine {
|
|
|
403
428
|
const removedTools = this.getSessionTools(sessionId);
|
|
404
429
|
this.registry.cleanupSession(sessionId);
|
|
405
430
|
this.index.bySession.delete(sessionId);
|
|
431
|
+
for (const tool of removedTools) {
|
|
432
|
+
// The agent index too, so a lookup by agent never names a tool that is gone.
|
|
433
|
+
this.removeIndexedToolEverywhere(tool.id);
|
|
434
|
+
}
|
|
435
|
+
if (this.onToolRemoved) {
|
|
436
|
+
// The executables go with the tools, as removeTool does. A host that
|
|
437
|
+
// cleans up through the orchestrator unregisters the same names; a
|
|
438
|
+
// second unregistration of a name finds nothing to do.
|
|
439
|
+
const unregister = this.onToolRemoved;
|
|
440
|
+
void Promise.allSettled(removedTools.map((tool) => unregister(tool)));
|
|
441
|
+
}
|
|
406
442
|
return removedTools;
|
|
407
443
|
}
|
|
408
444
|
/**
|
|
@@ -419,28 +455,34 @@ export class EmergentCapabilityEngine {
|
|
|
419
455
|
* @returns what happened, so a host can tell a registered tool from a refused one.
|
|
420
456
|
*/
|
|
421
457
|
async syncPersistedTool(tool) {
|
|
422
|
-
|
|
423
|
-
if (!
|
|
458
|
+
const existing = await this.registry.loadRow(tool.id);
|
|
459
|
+
if (!existing && this.registry.hasStorage()) {
|
|
424
460
|
// No stored row yet: the host hydrates from its own store. The row is
|
|
425
461
|
// written as it was before loading went through the stored row, so the
|
|
426
462
|
// tool's uses are recorded and the next load finds it.
|
|
427
463
|
await this.registry.writeToolRow(tool);
|
|
428
|
-
row = await this.registry.loadRow(tool.id);
|
|
429
464
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
465
|
+
// The row is read for the admission after any write above, so the write
|
|
466
|
+
// is not a change the admission's read missed.
|
|
467
|
+
return this.withRead(async (readAt) => {
|
|
468
|
+
const row = await this.registry.loadRow(tool.id);
|
|
469
|
+
if (row) {
|
|
470
|
+
// With source persistence off the row holds a redacted record; the
|
|
471
|
+
// implementation the host supplied is what runs then.
|
|
472
|
+
return this.admitRow(row, { readAt, sourceFallback: sourceFromImplementation(tool.implementation) });
|
|
473
|
+
}
|
|
474
|
+
const stored = await this.registry.readState(tool.id);
|
|
475
|
+
return this.admit({
|
|
476
|
+
toolId: tool.id,
|
|
477
|
+
name: tool.name,
|
|
478
|
+
source: sourceFromImplementation(tool.implementation),
|
|
479
|
+
stored,
|
|
480
|
+
requestStored: stored?.request != null,
|
|
481
|
+
legacyActive: tool.isActive ?? true,
|
|
482
|
+
tier: tool.tier,
|
|
483
|
+
createdBy: tool.createdBy,
|
|
484
|
+
buildTool: () => tool,
|
|
485
|
+
}, { readAt });
|
|
444
486
|
});
|
|
445
487
|
}
|
|
446
488
|
/**
|
|
@@ -493,19 +535,24 @@ export class EmergentCapabilityEngine {
|
|
|
493
535
|
throw new Error("selector_required: loadPersistedTools({ tiers: ['session'] }) needs the sessionId whose tools to load; " +
|
|
494
536
|
"a session's tools are private to it.");
|
|
495
537
|
}
|
|
496
|
-
const rows = await this.registry.loadRows(tiers, { agentId, sessionId });
|
|
497
538
|
const outcomes = [];
|
|
498
539
|
const failed = [];
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
540
|
+
// One read for the rows and their admissions: a tool that changes in this
|
|
541
|
+
// process after the rows were read (a removal, a registration) is read
|
|
542
|
+
// again before it is adopted.
|
|
543
|
+
await this.withRead(async (readAt) => {
|
|
544
|
+
const rows = await this.registry.loadRows(tiers, { agentId, sessionId });
|
|
545
|
+
for (const row of rows) {
|
|
546
|
+
try {
|
|
547
|
+
outcomes.push(await this.admitRow(row, { readAt }));
|
|
548
|
+
}
|
|
549
|
+
catch (error) {
|
|
550
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
551
|
+
failed.push({ toolId: row.id, name: row.name, error: message });
|
|
552
|
+
console.warn(`[agentos:emergent] stored tool "${row.name}" (${row.id}) did not load: ${message}`);
|
|
553
|
+
}
|
|
507
554
|
}
|
|
508
|
-
}
|
|
555
|
+
});
|
|
509
556
|
for (const outcome of outcomes) {
|
|
510
557
|
if (outcome.state === 'active')
|
|
511
558
|
continue;
|
|
@@ -579,24 +626,42 @@ export class EmergentCapabilityEngine {
|
|
|
579
626
|
* @returns the outcome, or `undefined` when the tool is unknown.
|
|
580
627
|
*/
|
|
581
628
|
async reactivateTool(toolId) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
629
|
+
return this.withRead(async (readAt) => {
|
|
630
|
+
const row = await this.registry.loadRow(toolId);
|
|
631
|
+
if (row) {
|
|
632
|
+
return this.admitRow(row, { force: true, readAt });
|
|
633
|
+
}
|
|
634
|
+
const tool = this.registry.get(toolId);
|
|
635
|
+
if (!tool) {
|
|
636
|
+
return undefined;
|
|
637
|
+
}
|
|
638
|
+
const held = this.registry.getState(toolId);
|
|
639
|
+
return this.admit({
|
|
640
|
+
toolId,
|
|
641
|
+
name: tool.name,
|
|
642
|
+
source: sourceFromImplementation(tool.implementation),
|
|
643
|
+
stored: held,
|
|
644
|
+
requestStored: held?.request != null,
|
|
645
|
+
legacyActive: true,
|
|
646
|
+
tier: tool.tier,
|
|
647
|
+
createdBy: tool.createdBy,
|
|
648
|
+
buildTool: () => tool,
|
|
649
|
+
}, { force: true, readAt });
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Run a read of stored rows and the admissions that use it. `readAt` is the
|
|
654
|
+
* point the read started at; an admission adopts only what has not changed
|
|
655
|
+
* since (see `EmergentToolRegistry.adopt`).
|
|
656
|
+
*/
|
|
657
|
+
async withRead(read) {
|
|
658
|
+
const readAt = this.registry.beginRead();
|
|
659
|
+
try {
|
|
660
|
+
return await read(readAt);
|
|
585
661
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
return undefined;
|
|
662
|
+
finally {
|
|
663
|
+
this.registry.endRead();
|
|
589
664
|
}
|
|
590
|
-
const held = this.registry.getState(toolId);
|
|
591
|
-
return this.admit({
|
|
592
|
-
toolId,
|
|
593
|
-
name: tool.name,
|
|
594
|
-
source: sourceFromImplementation(tool.implementation),
|
|
595
|
-
stored: held,
|
|
596
|
-
requestStored: held?.request != null,
|
|
597
|
-
legacyActive: true,
|
|
598
|
-
buildTool: () => tool,
|
|
599
|
-
}, { force: true });
|
|
600
665
|
}
|
|
601
666
|
admitRow(row, options = {}) {
|
|
602
667
|
const stored = row.state
|
|
@@ -632,6 +697,8 @@ export class EmergentCapabilityEngine {
|
|
|
632
697
|
requestStored: row.request_json != null,
|
|
633
698
|
legacyActive: !(row.is_active === 0 || row.is_active === false),
|
|
634
699
|
flagSynced: row.flag_synced == null ? undefined : !(row.flag_synced === 0 || row.flag_synced === false),
|
|
700
|
+
tier: row.tier,
|
|
701
|
+
createdBy: row.created_by_agent,
|
|
635
702
|
buildTool: (implementation) => toolFromRow(row, implementation),
|
|
636
703
|
}, options);
|
|
637
704
|
}
|
|
@@ -649,12 +716,17 @@ export class EmergentCapabilityEngine {
|
|
|
649
716
|
const { toolId, name } = candidate;
|
|
650
717
|
const depth = options.readmitted ?? 0;
|
|
651
718
|
if (depth < 2) {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
719
|
+
// The tool's queued writes and deletes land first, so the row read next
|
|
720
|
+
// is the one they leave.
|
|
721
|
+
await this.registry.settled(toolId);
|
|
722
|
+
return this.withRead(async (readAt) => {
|
|
723
|
+
const fresh = await this.registry.loadRow(toolId);
|
|
724
|
+
if (fresh) {
|
|
725
|
+
return this.admitRow(fresh, { ...options, readmitted: depth + 1, readAt });
|
|
726
|
+
}
|
|
727
|
+
await this.unregisterIfLive(toolId);
|
|
728
|
+
return { toolId, name, state: 'demoted', reason: 'removed' };
|
|
729
|
+
});
|
|
658
730
|
}
|
|
659
731
|
// Nothing is written; the tool is held off here until the next load.
|
|
660
732
|
this.holdStored(toolId, {
|
|
@@ -670,6 +742,9 @@ export class EmergentCapabilityEngine {
|
|
|
670
742
|
}
|
|
671
743
|
async admit(candidate, options = {}) {
|
|
672
744
|
const { toolId, name, source, stored, requestStored } = candidate;
|
|
745
|
+
// The point after which a change makes what this admission holds stale:
|
|
746
|
+
// where its row was read, or, for a caller that did not say, now.
|
|
747
|
+
const readAt = options.readAt ?? this.registry.generation(toolId);
|
|
673
748
|
let legacyActive = candidate.legacyActive;
|
|
674
749
|
// A row whose last state write did not finish its flag write: finish it
|
|
675
750
|
// first, so the flag reads what the state row says before anything is
|
|
@@ -756,6 +831,14 @@ export class EmergentCapabilityEngine {
|
|
|
756
831
|
allowlist: source.format === 'code-with-list' ? granted.filter((api) => listed.allowlist.includes(api)) : granted,
|
|
757
832
|
};
|
|
758
833
|
}
|
|
834
|
+
// 3a. An agent-tier row from an earlier release is owned by the forging
|
|
835
|
+
// GMI instance's id, which no persona can match, so no caller could
|
|
836
|
+
// ever pass the owner check: it loads suspended rather than running
|
|
837
|
+
// for everyone. Forging the tool again under the persona is the way
|
|
838
|
+
// back; `reactivateTool` goes through this same path.
|
|
839
|
+
if (!refusal && candidate.tier === 'agent' && candidate.createdBy.startsWith(GMI_INSTANCE_ID_PREFIX)) {
|
|
840
|
+
refusal = 'legacy_owner';
|
|
841
|
+
}
|
|
759
842
|
if (implementation && !refusal) {
|
|
760
843
|
refusal = this.refusalFor(implementation);
|
|
761
844
|
}
|
|
@@ -852,10 +935,22 @@ export class EmergentCapabilityEngine {
|
|
|
852
935
|
setBy: 'library',
|
|
853
936
|
at: Date.now(),
|
|
854
937
|
request,
|
|
855
|
-
});
|
|
938
|
+
}, readAt);
|
|
856
939
|
if (!adopted) {
|
|
857
|
-
//
|
|
858
|
-
|
|
940
|
+
// The tool changed in this process after its row was read (removed,
|
|
941
|
+
// replaced under its id, written again), or a removal of it is still
|
|
942
|
+
// deleting its rows: what this admission holds may be stale. With
|
|
943
|
+
// storage the row is read again once the tool's queued writes have run;
|
|
944
|
+
// without, the registry's own record is the answer.
|
|
945
|
+
if (this.registry.hasStorage()) {
|
|
946
|
+
return this.readmit(candidate, options);
|
|
947
|
+
}
|
|
948
|
+
const current = this.registry.get(toolId);
|
|
949
|
+
if (!current) {
|
|
950
|
+
return { toolId, name, state: 'demoted', reason: 'removed' };
|
|
951
|
+
}
|
|
952
|
+
const held = this.registry.getState(toolId);
|
|
953
|
+
return { toolId, name: current.name, state: held?.state ?? 'active', reason: held?.reason ?? null };
|
|
859
954
|
}
|
|
860
955
|
this.indexTool(tool.id, tool.createdBy, this.extractSessionId(tool.source) ?? `persisted:${tool.id}`);
|
|
861
956
|
if (this.onToolForged) {
|
|
@@ -870,17 +965,75 @@ export class EmergentCapabilityEngine {
|
|
|
870
965
|
this.removeIndexedToolEverywhere(toolId);
|
|
871
966
|
throw error;
|
|
872
967
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
await this.onToolRemoved(tool);
|
|
877
|
-
}
|
|
878
|
-
this.removeIndexedToolEverywhere(toolId);
|
|
879
|
-
return { toolId, name, state: 'demoted', reason: 'removed' };
|
|
968
|
+
const settled = await this.settleRegistration(tool);
|
|
969
|
+
if (settled) {
|
|
970
|
+
return settled;
|
|
880
971
|
}
|
|
881
972
|
}
|
|
882
973
|
return { toolId, name, state: 'active', reason: null };
|
|
883
974
|
}
|
|
975
|
+
/**
|
|
976
|
+
* After the host registered a tool's executable, the registry must still
|
|
977
|
+
* hold that very object. When it does not (the tool was removed, or
|
|
978
|
+
* replaced under its id, while the registration ran), the executor is
|
|
979
|
+
* brought back in line with the registry: the current tool's executable is
|
|
980
|
+
* registered (the old name's taken out first when the name changed), or
|
|
981
|
+
* the stale executable is taken out when the registry holds none. The
|
|
982
|
+
* registry is read again after every registration, so a change during the
|
|
983
|
+
* settlement is settled too, up to three rounds. A registration that fails,
|
|
984
|
+
* or a tool still changing after three rounds, leaves no executable under
|
|
985
|
+
* the name and throws: a tool that cannot be called is reported, never a
|
|
986
|
+
* stale executable left running. Returns the outcome to report, or
|
|
987
|
+
* undefined when the first registration stands.
|
|
988
|
+
*/
|
|
989
|
+
async settleRegistration(tool) {
|
|
990
|
+
let registered = tool;
|
|
991
|
+
for (let round = 0; round < 3; round += 1) {
|
|
992
|
+
const current = this.registry.get(tool.id);
|
|
993
|
+
if (current === registered) {
|
|
994
|
+
if (round === 0) {
|
|
995
|
+
return undefined;
|
|
996
|
+
}
|
|
997
|
+
if (!current) {
|
|
998
|
+
this.removeIndexedToolEverywhere(tool.id);
|
|
999
|
+
return { toolId: tool.id, name: tool.name, state: 'demoted', reason: 'removed' };
|
|
1000
|
+
}
|
|
1001
|
+
const held = this.registry.getState(tool.id);
|
|
1002
|
+
return { toolId: tool.id, name: current.name, state: held?.state ?? 'active', reason: held?.reason ?? null };
|
|
1003
|
+
}
|
|
1004
|
+
try {
|
|
1005
|
+
if (registered && (!current || current.name !== registered.name) && this.onToolRemoved) {
|
|
1006
|
+
await this.onToolRemoved(registered);
|
|
1007
|
+
}
|
|
1008
|
+
if (current && this.onToolForged) {
|
|
1009
|
+
await this.onToolForged(current, this.createExecutableTool(current));
|
|
1010
|
+
}
|
|
1011
|
+
registered = current;
|
|
1012
|
+
}
|
|
1013
|
+
catch (error) {
|
|
1014
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1015
|
+
await this.dropExecutable(registered ?? current ?? tool);
|
|
1016
|
+
if (!this.registry.get(tool.id)) {
|
|
1017
|
+
this.removeIndexedToolEverywhere(tool.id);
|
|
1018
|
+
}
|
|
1019
|
+
throw new Error(`the executable of "${tool.name}" (${tool.id}) could not be brought in line with the registry: ${message}`);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
await this.dropExecutable(registered ?? tool);
|
|
1023
|
+
throw new Error(`the executable of "${tool.name}" (${tool.id}) could not be settled: the tool kept changing while it was registered`);
|
|
1024
|
+
}
|
|
1025
|
+
/** Take a tool's executable out of the host, best-effort. */
|
|
1026
|
+
async dropExecutable(tool) {
|
|
1027
|
+
if (!this.onToolRemoved) {
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
try {
|
|
1031
|
+
await this.onToolRemoved(tool);
|
|
1032
|
+
}
|
|
1033
|
+
catch (error) {
|
|
1034
|
+
console.warn(`[agentos:emergent] could not take out the executable of "${tool.name}" (${tool.id}):`, error instanceof Error ? error.message : error);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
884
1037
|
/**
|
|
885
1038
|
* The library's reason for not running an implementation under the
|
|
886
1039
|
* configuration in force, or `null`. Later steps add their checks here, so
|
|
@@ -1064,11 +1217,12 @@ export class EmergentCapabilityEngine {
|
|
|
1064
1217
|
// The owner is the persona that forged the tool, compared as it was
|
|
1065
1218
|
// stored ('unknown' for a caller without one). A row from an earlier
|
|
1066
1219
|
// release holds the forging GMI instance's id instead, which no
|
|
1067
|
-
// persona can match
|
|
1068
|
-
//
|
|
1220
|
+
// persona can match; the loader suspends such a row (`legacy_owner`),
|
|
1221
|
+
// and a call to one held in memory is refused here like any other
|
|
1222
|
+
// owner mismatch.
|
|
1069
1223
|
const owner = current.createdBy;
|
|
1070
1224
|
const caller = context.personaId ?? 'unknown';
|
|
1071
|
-
if (current.tier === 'agent' &&
|
|
1225
|
+
if (current.tier === 'agent' && caller !== owner) {
|
|
1072
1226
|
return {
|
|
1073
1227
|
success: false,
|
|
1074
1228
|
error: `Emergent tool "${tool.name}" belongs to agent ${owner}; it is not callable as ${caller}.`,
|